���ѧۧݧ�ӧ�� �ާ֧ߧ֧էا֧� - ���֧էѧܧ�ڧ��ӧѧ�� - /home3/cpr76684/public_html/behat.tar
���ѧ٧ѧ�
behat_permissions.php 0000644 00000027513 15151222201 0010772 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Steps definitions related with permissions. * * @package core * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Mink\Exception\ExpectationException as ExpectationException, Behat\Gherkin\Node\TableNode as TableNode; /** * Steps definitions to set up permissions to capabilities. * * @package core * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_permissions extends behat_base { /** * Set system level permissions to the specified role. Expects a table with capability name and permission (Inherit/Allow/Prevent/Prohibit) columns. * @Given /^I set the following system permissions of "(?P<rolefullname_string>(?:[^"]|\\")*)" role:$/ * @param string $rolename * @param TableNode $table */ public function i_set_the_following_system_permissions_of_role($rolename, $table) { $parentnodes = get_string('users', 'admin') . ' > ' . get_string('permissions', 'role'); // Go to home page. $this->execute("behat_general::i_am_on_homepage"); // Navigate to course management page via navigation block. $this->execute("behat_navigation::i_navigate_to_in_site_administration", array($parentnodes . ' > ' . get_string('defineroles', 'role')) ); $this->execute("behat_general::click_link", "Edit " . $this->escape($rolename) . " role"); $this->execute("behat_permissions::i_fill_the_capabilities_form_with_the_following_permissions", $table); $this->execute('behat_forms::press_button', get_string('savechanges')); } /** * Overrides system capabilities at category, course and module levels. This step begins after clicking 'Permissions' link. Expects a table with capability name and permission (Inherit/Allow/Prevent/Prohibit) columns. * @Given /^I override the system permissions of "(?P<rolefullname_string>(?:[^"]|\\")*)" role with:$/ * @param string $rolename * @param TableNode $table */ public function i_override_the_system_permissions_of_role_with($rolename, $table) { // We don't know the number of overrides so we have to get it to match the option contents. $roleoption = $this->find('xpath', '//select[@name="roleid"]/option[contains(.,"' . $this->escape($rolename) . '")]'); $this->execute('behat_forms::i_set_the_field_to', array(get_string('advancedoverride', 'role'), $this->escape($roleoption->getText())) ); if (!$this->running_javascript()) { $xpath = "//div[@class='advancedoverride']/div/form/noscript"; $this->execute("behat_general::i_click_on_in_the", [ get_string('go'), 'button', $this->escape($xpath), 'xpath_element'] ); } $this->execute("behat_permissions::i_fill_the_capabilities_form_with_the_following_permissions", $table); $this->execute('behat_forms::press_button', get_string('savechanges')); } /** * Fills the advanced permissions form with the provided data. Expects a table with capability name and permission (Inherit/Allow/Prevent/Prohibit) columns. * @Given /^I fill the capabilities form with the following permissions:$/ * @param TableNode $table * @return void */ public function i_fill_the_capabilities_form_with_the_following_permissions($table) { // Ensure we are using the advanced view. // Wrapped in a try/catch to capture the exception and continue execution, we don't know if advanced mode was already enabled. try { $advancedtoggle = $this->find_button(get_string('showadvanced', 'form')); if ($advancedtoggle) { $advancedtoggle->click(); // Wait for the page to load. $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS); } } catch (Exception $e) { // We already are in advanced mode. } // Using getRows() as we are not sure if tests writers will add the header. foreach ($table->getRows() as $key => $row) { if (count($row) !== 2) { throw new ExpectationException('You should specify a table with capability/permission columns', $this->getSession()); } list($capability, $permission) = $row; // Skip the headers row if it was provided if (strtolower($capability) == 'capability' || strtolower($capability) == 'capabilities') { continue; } // Checking the permission value. $permissionconstant = 'CAP_'. strtoupper($permission); if (!defined($permissionconstant)) { throw new ExpectationException( 'The provided permission value "' . $permission . '" is not valid. Use Inherit, Allow, Prevent or Prohibited', $this->getSession() ); } // Converting from permission to constant value. $permissionvalue = constant($permissionconstant); // Here we wait for the element to appear and exception if it does not exist. $radio = $this->find('xpath', '//input[@name="' . $capability . '" and @value="' . $permissionvalue . '"]'); $field = behat_field_manager::get_field_instance('radio', $radio, $this->getSession()); $field->set_value(1); } } /** * Checks if the capability has the specified permission. Works in the role definition advanced page. * * @Then /^"(?P<capability_string>(?:[^"]|\\")*)" capability has "(?P<permission_string>Not set|Allow|Prevent|Prohibit)" permission$/ * @throws ExpectationException * @param string $capabilityname * @param string $permission * @return void */ public function capability_has_permission($capabilityname, $permission) { // We already know the name, so we just need the value. $radioxpath = "//table[contains(concat(' ', normalize-space(@class), ' '), ' rolecap ')]/descendant::input[@type='radio']" . "[@name='" . $capabilityname . "'][@checked]"; $checkedradio = $this->find('xpath', $radioxpath); switch ($permission) { case get_string('notset', 'role'): $perm = CAP_INHERIT; break; case get_string('allow', 'role'): $perm = CAP_ALLOW; break; case get_string('prevent', 'role'): $perm = CAP_PREVENT; break; case get_string('prohibit', 'role'): $perm = CAP_PROHIBIT; break; default: throw new ExpectationException('"' . $permission . '" permission does not exist', $this->getSession()); break; } if ($checkedradio->getAttribute('value') != $perm) { throw new ExpectationException('"' . $capabilityname . '" permission is not "' . $permission . '"', $this->getSession()); } } /** * Set the allowed role assignments for the specified role. * * @Given /^I define the allowed role assignments for the "(?P<rolefullname_string>(?:[^"]|\\")*)" role as:$/ * @param string $rolename * @param TableNode $table * @return void Executes other steps */ public function i_define_the_allowed_role_assignments_for_a_role_as($rolename, $table) { $parentnodes = get_string('users', 'admin') . ' > ' . get_string('permissions', 'role'); // Go to home page. $this->execute("behat_general::i_am_on_homepage"); // Navigate to Define roles page via site administration menu. $this->execute("behat_navigation::i_navigate_to_in_site_administration", $parentnodes .' > '. get_string('defineroles', 'role') ); $this->execute("behat_general::click_link", "Allow role assignments"); $this->execute("behat_permissions::i_fill_in_the_allowed_role_assignments_form_for_a_role_with", array($rolename, $table) ); $this->execute('behat_forms::press_button', get_string('savechanges')); } /** * Fill in the allowed role assignments form for the specied role. * * Takes a table with two columns. Each row should contain the target * role, and either "Assignable" or "Not assignable". * * @Given /^I fill in the allowed role assignments form for the "(?P<rolefullname_string>(?:[^"]|\\")*)" role with:$/ * @param String $sourcerole * @param TableNode $table * @return void */ public function i_fill_in_the_allowed_role_assignments_form_for_a_role_with($sourcerole, $table) { foreach ($table->getRows() as $key => $row) { list($targetrole, $allowed) = $row; $node = $this->find('xpath', '//input[@title="Allow users with role ' . $sourcerole . ' to assign the role ' . $targetrole . '"]'); if ($allowed == 'Assignable') { if (!$node->isChecked()) { $node->check(); } } else if ($allowed == 'Not assignable') { if ($node->isChecked()) { $node->uncheck(); } } else { throw new ExpectationException( 'The provided permission value "' . $allowed . '" is not valid. Use Assignable, or Not assignable', $this->getSession() ); } } } /** * Mark context as frozen. * * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" is context frozen$/ * @throws ExpectationException if the context cannot be frozen or found * @param string $element Element we look on * @param string $selector The type of where we look (activity, course) */ public function the_context_is_context_frozen(string $element, string $selector) { // Enable context freeze if it is not done yet. set_config('contextlocking', 1); // Find context. $context = self::get_context($selector, $element); // Freeze context. $context->set_locked(true); } /** * Unmark context as frozen. * * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" is not context frozen$/ * @throws ExpectationException if the context cannot be frozen or found * @param string $element Element we look on * @param string $selector The type of where we look (activity, course) */ public function the_context_is_not_context_frozen(string $element, string $selector) { // Enable context freeze if it is not done yet. set_config('contextlocking', 1); // Find context. $context = self::get_context($selector, $element); // Freeze context. $context->set_locked(false); } } locking.feature 0000644 00000021062 15151222201 0007537 0 ustar 00 @core Feature: Context freezing apply to child contexts In order to preserve content As a manager I can disbale writes at different areas Background: Given the following config values are set as admin: | contextlocking | 1 | And the following "users" exist: | username | firstname | lastname | email | | teacher | Ateacher | Teacher | teacher@example.com | | student1 | Astudent | Astudent | student1@example.com | And the following "categories" exist: | name | category | idnumber | | cata | 0 | cata | | cataa | cata | cataa | | catb | 0 | catb | And the following "courses" exist: | fullname | shortname | category | | courseaa1 | courseaa1 | cataa | | courseaa2 | courseaa2 | cataa | | courseb | courseb | catb | And the following "activities" exist: | activity | name | course | idnumber | | forum | faa1 | courseaa1 | faa1 | | forum | faa1b | courseaa1 | faa1b | | forum | faa2 | courseaa2 | faa2 | | forum | fb | courseb | fb | And the following "course enrolments" exist: | user | course | role | | teacher | courseaa1 | editingteacher | | student1 | courseaa1 | student | | teacher | courseaa2 | editingteacher | | student1 | courseaa2 | student | | teacher | courseb | editingteacher | | student1 | courseb | student | Scenario: Freeze course module module should freeze just that module Given I am on the "courseaa1" "Course" page logged in as "admin" And I follow "faa1" And "Add discussion topic" "link" should exist When I follow "Freeze this context" And I click on "Continue" "button" Then "Add discussion topic" "link" should not exist When I am on "courseaa1" course homepage Then edit mode should be available on the current page When I follow "faa1b" Then "Add discussion topic" "link" should exist When I am on "courseaa2" course homepage Then edit mode should be available on the current page When I follow "faa2" Then "Add discussion topic" "link" should exist When I am on "courseb" course homepage Then edit mode should be available on the current page When I follow "fb" Then "Add discussion topic" "link" should exist And I log out When I am on the "courseaa1" "Course" page logged in as "teacher" And I follow "faa1" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage Then edit mode should be available on the current page When I follow "faa1b" Then "Add discussion topic" "link" should exist When I am on "courseaa2" course homepage Then edit mode should be available on the current page When I follow "faa2" Then "Add discussion topic" "link" should exist When I am on "courseb" course homepage Then edit mode should be available on the current page When I follow "fb" And "Add discussion topic" "link" should exist And I log out When I am on the "courseaa1" "Course" page logged in as "student1" And I follow "faa1" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage When I follow "faa1b" Then "Add discussion topic" "link" should exist When I am on "courseaa2" course homepage When I follow "faa2" Then "Add discussion topic" "link" should exist When I am on "courseb" course homepage When I follow "fb" Then "Add discussion topic" "link" should exist Scenario: Freeze course should freeze all children Given I am on the "courseaa1" "Course" page logged in as "admin" Then edit mode should be available on the current page When I follow "Freeze this context" And I click on "Continue" "button" Then edit mode should not be available on the current page Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage Then edit mode should not be available on the current page And "Unfreeze this context" "link" should exist in current page administration When I follow "faa1b" Then "Add a new discussion topic" "link" should not exist And "Unfreeze this context" "link" should not exist in current page administration When I am on "courseaa2" course homepage Then edit mode should be available on the current page When I follow "faa2" Then "Add discussion topic" "link" should exist When I am on "courseb" course homepage Then edit mode should be available on the current page When I follow "fb" Then "Add discussion topic" "link" should exist And I log out When I am on the "courseaa1" "Course" page logged in as "teacher" And I follow "faa1" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage Then edit mode should not be available on the current page When I follow "faa1b" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa2" course homepage Then edit mode should be available on the current page When I follow "faa2" Then "Add discussion topic" "link" should exist When I am on "courseb" course homepage Then edit mode should be available on the current page When I follow "fb" Then "Add discussion topic" "link" should exist And I log out When I am on the "courseaa1" "Course" page logged in as "student1" And I follow "faa1" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage When I follow "faa1b" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa2" course homepage When I follow "faa2" Then "Add discussion topic" "link" should exist When I am on "courseb" course homepage When I follow "fb" Then "Add discussion topic" "link" should exist Scenario: Freeze course category should freeze all children Given I log in as "admin" And I go to the courses management page And I click on "managecontextlock" action for "cata" in management category listing And I click on "Continue" "button" And I am on "courseaa1" course homepage Then edit mode should not be available on the current page Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage Then edit mode should not be available on the current page And "Unfreeze this context" "link" should not exist in current page administration When I follow "faa1b" Then "Add a new discussion topic" "link" should not exist And "Unfreeze this context" "link" should not exist in current page administration When I am on "courseaa2" course homepage Then edit mode should not be available on the current page When I follow "faa2" Then "Add a new discussion topic" "link" should not exist And "Unfreeze this context" "link" should not exist in current page administration When I am on "courseb" course homepage Then edit mode should be available on the current page When I follow "fb" Then "Add discussion topic" "link" should exist And I log out When I am on the "courseaa1" "Course" page logged in as "teacher" Then edit mode should not be available on the current page And I follow "faa1" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage Then edit mode should not be available on the current page When I follow "faa1b" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa2" course homepage Then edit mode should not be available on the current page When I follow "faa2" Then "Add a new discussion topic" "link" should not exist When I am on "courseb" course homepage Then edit mode should be available on the current page When I follow "fb" Then "Add discussion topic" "link" should exist And I log out When I am on the "courseaa1" "Course" page logged in as "student1" And I follow "faa1" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa1" course homepage When I follow "faa1b" Then "Add a new discussion topic" "link" should not exist When I am on "courseaa2" course homepage When I follow "faa2" Then "Add a new discussion topic" "link" should not exist When I am on "courseb" course homepage When I follow "fb" Then "Add discussion topic" "link" should exist min_max_version.feature 0000644 00000001772 15151222201 0011314 0 ustar 00 @core Feature: Check for minimum or maximimum version of Moodle In order adapt acceptance tests for different versions of Moodle As a developer I should be able to skip tests according to the Moodle version present on a site Scenario: Minimum version too low Given the site is running Moodle version 99.0 or higher # The following steps should not be executed. If they are, the test will fail. When I log in as "admin" Then I should not see "Home" Scenario: Maximum version too high Given the site is running Moodle version 3.0 or lower # The following steps should not be executed. If they are, the test will fail. When I log in as "admin" Then I should not see "Home" Scenario: Minimum version OK Given the site is running Moodle version 3.0 or higher When I log in as "admin" Then I should see "Home" Scenario: Maximum version OK Given the site is running Moodle version 99.0 or lower When I log in as "admin" Then I should see "Home" enabledashboard.feature 0000644 00000001516 15151222201 0011211 0 ustar 00 @core Feature: Enable dashboard setting In order to hide/show dashboard in navigation As an administrator I can enable or disable it Scenario: Hide setting when dashboard is disabled Given the following config values are set as admin: | enabledashboard | 0 | # 2 = User preference. | defaulthomepage | 2 | When I log in as "admin" And I navigate to "Appearance > Navigation" in site administration Then the field "Enable Dashboard" matches value "0" And I should not see "Allow guest access to Dashboard" And I should not see "Dashboard" in the "Start page for users" "select" And I follow "Appearance" And I should not see "Default Dashboard page" And I follow "Preferences" in the user menu And I follow "Start page" And I should not see "Dashboard" in the "Start page" "select" behat_action_menu.php 0000644 00000020167 15151222201 0010716 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Steps definitions to open and close action menus. * * @package core * @category test * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\DriverException; /** * Steps definitions to open and close action menus. * * @package core * @category test * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_action_menu extends behat_base { /** * Open the action menu in * * @Given /^I open the action menu in "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ * @param string $element * @param string $selector * @return void */ public function i_open_the_action_menu_in($element, $selectortype) { // Gets the node based on the requested selector type and locator. $node = $this->get_node_in_container( "css_element", "[role=button][aria-haspopup=true],[role=menuitem][aria-haspopup=true]", $selectortype, $element ); // Check if it is not already opened. if ($node->getAttribute('aria-expanded') === 'true') { return; } $this->ensure_node_is_visible($node); $node->click(); } /** * When an action menu is open, follow one of the items in it. * * @Given /^I choose "(?P<link_string>(?:[^"]|\\")*)" in the open action menu$/ * @param string $linkstring * @return void */ public function i_choose_in_the_open_action_menu($menuitemstring) { if (!$this->running_javascript()) { throw new DriverException('Action menu steps are not available with Javascript disabled'); } // Gets the node based on the requested selector type and locator. $menuselector = ".moodle-actionmenu .dropdown.show .dropdown-menu"; $node = $this->get_node_in_container("link", $menuitemstring, "css_element", $menuselector); $this->ensure_node_is_visible($node); $node->click(); } /** * Select a specific item in an action menu. * * @When /^I choose the "(?P<item_string>(?:[^"]|\\")*)" item in the "(?P<actionmenu_string>(?:[^"]|\\")*)" action menu$/ * @param string $item The item to choose * @param string $actionmenu The text used in the description of the action menu */ public function i_choose_in_the_named_menu(string $item, string $actionmenu): void { $menu = $this->find('actionmenu', $actionmenu); $this->select_item_in_action_menu($item, $menu); } /** * Select a specific item in an action menu within a container. * * @When /^I choose the "(?P<item_string>(?:[^"]|\\")*)" item in the "(?P<actionmenu_string>(?:[^"]|\\")*)" action menu of the "(?P<locator_string>(?:[^"]|\\")*)" "(?P<type_string>(?:[^"]|\\")*)"$/ * @param string $item The item to choose * @param string $actionmenu The text used in the description of the action menu * @param string|NodeElement $locator The identifer used for the container * @param string $selector The type of container to locate */ public function i_choose_in_the_named_menu_in_container(string $item, string $actionmenu, $locator, $selector): void { $container = $this->find($selector, $locator); $menu = $this->find('actionmenu', $actionmenu, false, $container); $this->select_item_in_action_menu($item, $menu); } /** * Select an item in the specified menu. * * Note: This step does work both with, and without, JavaScript. * * @param string $item Item string value * @param NodeElement $menu The menu NodeElement to select from */ protected function select_item_in_action_menu(string $item, NodeElement $menu): void { if ($this->running_javascript()) { // Open the menu by clicking on the trigger. $this->execute( 'behat_general::i_click_on', [$menu, "NodeElement"] ); } // Select the menu item. $this->execute( 'behat_general::i_click_on_in_the', [$item, "link", $menu, "NodeElement"] ); } /** * The action menu item should not exist. * * @Then /^the "(?P<item_string>(?:[^"]|\\")*)" item should not exist in the "(?P<actionmenu_string>(?:[^"]|\\")*)" action menu$/ * @param string $item The item to check * @param string $actionmenu The text used in the description of the action menu */ public function item_should_not_exist(string $item, string $actionmenu): void { $menu = $this->find('actionmenu', $actionmenu); $this->execute('behat_general::should_not_exist_in_the', [ $item, 'link', $menu, 'NodeElement' ]); } /** * The action menu item should not exist within a container. * * @Then /^the "(?P<item_string>(?:[^"]|\\")*)" item should not exist in the "(?P<actionmenu_string>(?:[^"]|\\")*)" action menu of the "(?P<locator_string>(?:[^"]|\\")*)" "(?P<type_string>(?:[^"]|\\")*)"$/ * @param string $item The item to check * @param string $actionmenu The text used in the description of the action menu * @param string|NodeElement $locator The identifer used for the container * @param string $selector The type of container to locate */ public function item_should_not_exist_in_the(string $item, string $actionmenu, $locator, $selector): void { $container = $this->find($selector, $locator); $menu = $this->find('actionmenu', $actionmenu, false, $container); $this->execute('behat_general::should_not_exist_in_the', [ $item, 'link', $menu, 'NodeElement' ]); } /** * The action menu item should exist. * * @Then /^the "(?P<item_string>(?:[^"]|\\")*)" item should exist in the "(?P<actionmenu_string>(?:[^"]|\\")*)" action menu$/ * @param string $item The item to check * @param string $actionmenu The text used in the description of the action menu */ public function item_should_exist(string $item, string $actionmenu): void { $menu = $this->find('actionmenu', $actionmenu); $this->execute('behat_general::should_exist_in_the', [ $item, 'link', $menu, 'NodeElement' ]); } /** * The action menu item should exist within a container. * * @Then /^the "(?P<item_string>(?:[^"]|\\")*)" item should exist in the "(?P<actionmenu_string>(?:[^"]|\\")*)" action menu of the "(?P<locator_string>(?:[^"]|\\")*)" "(?P<type_string>(?:[^"]|\\")*)"$/ * @param string $item The item to check * @param string $actionmenu The text used in the description of the action menu * @param string|NodeElement $locator The identifer used for the container * @param string $selector The type of container to locate */ public function item_should_exist_in_the(string $item, string $actionmenu, $locator, $selector): void { $container = $this->find($selector, $locator); $menu = $this->find('actionmenu', $actionmenu, false, $container); $this->execute('behat_general::should_exist_in_the', [ $item, 'link', $menu, 'NodeElement' ]); } } action_menu.feature 0000644 00000002161 15151222201 0010411 0 ustar 00 @core Feature: Navigate action menu In order to navigate an action menu As a user I need to be able to use the keyboard @javascript Scenario: The menu does not close on keyboard navigation When I log in as "admin" # Click to open the user menu. And I click on ".usermenu a.toggle-display" "css_element" in the ".usermenu" "css_element" # The menu should now be visible. Then ".usermenu [role='menu']" "css_element" should be visible # Press down arrow. And I press the down key # The menu should still be visible. And ".usermenu [role='menu']" "css_element" should be visible @javascript Scenario: The menu closes when it clicked outside When I log in as "admin" # Click to open the user menu. And I click on ".usermenu a.toggle-display" "css_element" in the ".usermenu" "css_element" # The menu should now be visible. Then ".usermenu [role='menu']" "css_element" should be visible # Click outside the menu. And I click on "adminsearchquery" "field" # The menu should now be hidden. And ".usermenu [role='menu']" "css_element" should not be visible permissionmanager.feature 0000644 00000007177 15151222201 0011647 0 ustar 00 @core @javascript Feature: Override permissions on a context In order to extend and restrict moodle features As an admin or a teacher I need to allow/deny the existing capabilities at different levels Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | t1@example.com | And the following "courses" exist: | fullname | shortname | enablecompletion | | Course 1 | C1 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Default system capabilities modification Given I am on the "C1" "permissions" page logged in as "admin" When I click on "Allow" "icon" in the "mod/forum:addnews" "table_row" And I press "Student" Then "Add announcementsmod/forum:addnews" row "Roles with permission" column of "permissions" table should contain "Student" When I reload the page And I click on "Delete Student role" "link" in the "mod/forum:addnews" "table_row" And I click on "Remove" "button" in the "Confirm role change" "dialogue" Then "Add announcementsmod/forum:addnews" row "Roles with permission" column of "permissions" table should not contain "Student" When I reload the page And I click on "Prohibit" "icon" in the "mod/forum:addnews" "table_row" And I press "Student" Then "Add announcementsmod/forum:addnews" row "Prohibited" column of "permissions" table should contain "Student" Scenario: Module capabilities overrides Given the following "activity" exists: | course | C1 | | activity | forum | | name | Forum 1 | And I am on the "Forum 1" "forum activity permissions" page logged in as admin When I click on "Allow" "icon" in the "mod/forum:addnews" "table_row" And I press "Student" Then "Add announcementsmod/forum:addnews" row "Roles with permission" column of "permissions" table should contain "Student" When I reload the page And I click on "Delete Student role" "link" in the "mod/forum:addnews" "table_row" And I click on "Remove" "button" in the "Confirm role change" "dialogue" Then "Add announcementsmod/forum:addnews" row "Roles with permission" column of "permissions" table should not contain "Student" When I reload the page And I click on "Prohibit" "icon" in the "mod/forum:addnews" "table_row" And I press "Student" Then "Add announcementsmod/forum:addnews" row "Prohibited" column of "permissions" table should contain "Student" Scenario: Dates, completion and description are not shown in permission and override pages Given the following "activity" exists: | course | C1 | | activity | feedback | | name | Test Feedback | | intro | Test feedback description | | completion | 1 | | timeopen | ##1 Jan 2040 08:00## | And I am on the "Test Feedback" "feedback activity" page logged in as teacher1 And I should see "Test feedback description" And "Mark as done" "button" should exist And I should see "1 January 2040" When I am on the "Test Feedback" "feedback activity permissions" page Then I should not see "Test feedback description" And "Mark as done" "button" should not exist And I should not see "1 January 2040" And I set the field "Advanced role override" to "Student" And I should not see "Test feedback description" And "Mark as done" "button" should not exist And I should not see "1 January 2040" readonlyform.feature 0000644 00000003202 15151222201 0010606 0 ustar 00 @core Feature: Read-only forms should work In order to use certain forms on large Moodle installations As a user Relevant featuers of non-editable forms should still work @javascript Scenario: Shortforms expand collapsing should work for read-only forms - one-section form Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | label | L1 | <a href="../lib/tests/fixtures/readonlyform.php?sections=1">Fixture link</a> | C1 | label1 | Given I am on the "C1" "Course" page logged in as "admin" And I follow "Fixture link" When I expand all fieldsets Then the field "Name" matches value "Important information" @javascript Scenario: Shortforms expand collapsing should work for read-only forms - two-section form Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | label | L1 | <a href="../lib/tests/fixtures/readonlyform.php?sections=2">Fixture link</a> | C1 | label1 | Given I am on the "C1" "Course" page logged in as "admin" And I follow "Fixture link" When I expand all fieldsets Then the field "Name" matches value "Important information" Then the field "Other" matches value "Other information" behat_deprecated.php 0000644 00000004146 15151222201 0010514 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../lib/behat/behat_deprecated_base.php'); use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; /** * Steps definitions that are now deprecated and will be removed in the next releases. * * This file only contains the steps that previously were in the behat_*.php files in the SAME DIRECTORY. * When deprecating steps from other components or plugins, create a behat_COMPONENT_deprecated.php * file in the same directory where the steps were defined. * * @package core * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_deprecated extends behat_deprecated_base { /** * Clicks link with specified id|title|alt|text in the flat navigation drawer. * * @When /^I select "(?P<link_string>(?:[^"]|\\")*)" from flat navigation drawer$/ * @param string $link * @deprecated Since Moodle 4.0 */ public function i_select_from_flat_navigation_drawer(string $link) { $this->deprecated_message(['i_select_from_primary_navigation', 'i_select_from_secondary_navigation']); $this->execute('behat_navigation::i_open_flat_navigation_drawer'); $this->execute('behat_general::i_click_on_in_the', [$link, 'link', '#nav-drawer', 'css_element']); } } largeforms.feature 0000644 00000005204 15151222201 0010252 0 ustar 00 @core Feature: Forms with a large number of fields In order to use certain forms on large Moodle installations As an admin I need forms to work with more fields than the PHP max_input_vars setting Background: # Get to the fixture page. Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | label | L1 | <a href="../lib/tests/fixtures/max_input_vars.php">FixtureLink</a> | C1 | label1 | When I am on the "C1" "Course" page logged in as "admin" And I follow "FixtureLink" # Note: These tests do not actually use JavaScript but they don't work with # the headless 'browser'. @javascript Scenario: Small form with checkboxes (not using workaround) When I follow "Advanced checkboxes / Small" And I press "Submit here!" Then I should see "_qf__core_max_input_vars_form=1" And I should see "mform_isexpanded_id_general=1" And I should see "arraytest=[13,42]" And I should see "array2test=[13,42]" And I should see "submitbutton=Submit here!" And I should see "Bulk checkbox success: true" @javascript Scenario: Small form with array fields (not using workaround) When I follow "Select options / Small" And I press "Submit here!" Then I should see "_qf__core_max_input_vars_form=1" And I should see "mform_isexpanded_id_general=1" And I should see "arraytest=[13,42]" And I should see "array2test=[13,42]" And I should see "submitbutton=Submit here!" And I should see "Bulk array success: true" @javascript Scenario: Below limit form with array fields (uses workaround but doesn't need it) When I follow "Select options / Below limit" And I press "Submit here!" Then I should see "_qf__core_max_input_vars_form=1" And I should see "mform_isexpanded_id_general=1" And I should see "arraytest=[13,42]" And I should see "array2test=[13,42]" And I should see "submitbutton=Submit here!" And I should see "Bulk array success: true" @javascript Scenario: Exact PHP limit length form with array fields (uses workaround but doesn't need it) When I follow "Select options / Exact PHP limit" And I press "Submit here!" Then I should see "_qf__core_max_input_vars_form=1" And I should see "mform_isexpanded_id_general=1" And I should see "arraytest=[13,42]" And I should see "array2test=[13,42]" And I should see "submitbutton=Submit here!" And I should see "Bulk array success: true" behat_forms.php 0000644 00000100711 15151222201 0007535 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Steps definitions related with forms. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../lib/behat/behat_base.php'); require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php'); use Behat\Gherkin\Node\{TableNode, PyStringNode}; use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\{ElementNotFoundException, ExpectationException}; /** * Forms-related steps definitions. * * Note, Behat tests to verify that the steps defined here work as advertised * are kept in admin/tool/behat/tests/behat. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_forms extends behat_base { /** * Presses button with specified id|name|title|alt|value. * * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $button */ public function press_button($button) { $this->execute('behat_general::i_click_on', [$button, 'button']); } /** * Press button with specified id|name|title|alt|value and switch to main window. * * @When /^I press "(?P<button_string>(?:[^"]|\\")*)" and switch to main window$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $button */ public function press_button_and_switch_to_main_window($button) { // Ensures the button is present, before pressing. $buttonnode = $this->find_button($button); $buttonnode->press(); $this->wait_for_pending_js(); $this->look_for_exceptions(); // Switch to main window. $this->execute('behat_general::switch_to_the_main_window'); } /** * Fills a form with field/value data. * * @Given /^I set the following fields to these values:$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param TableNode $data */ public function i_set_the_following_fields_to_these_values(TableNode $data) { // Expand all fields in case we have. $this->expand_all_fields(); $datahash = $data->getRowsHash(); // The action depends on the field type. foreach ($datahash as $locator => $value) { $this->set_field_value($locator, $value); } } /** * Fills a form with field/value data. * * @Given /^I set the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to these values:$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $containerelement Element we look in * @param string $containerselectortype The type of selector where we look in * @param TableNode $data */ public function i_set_the_following_fields_in_container_to_these_values( $containerelement, $containerselectortype, TableNode $data) { // Expand all fields in case we have. $this->expand_all_fields(); $datahash = $data->getRowsHash(); // The action depends on the field type. foreach ($datahash as $locator => $value) { $this->set_field_value_in_container($locator, $value, $containerselectortype, $containerelement); } } /** * Expands all moodleform's fields, including collapsed fieldsets and advanced fields if they are present. * @Given /^I expand all fieldsets$/ */ public function i_expand_all_fieldsets() { $this->expand_all_fields(); } /** * Expands all moodle form fieldsets if they exists. * * Externalized from i_expand_all_fields to call it from * other form-related steps without having to use steps-group calls. * * @throws ElementNotFoundException Thrown by behat_base::find_all * @return void */ protected function expand_all_fields() { // Expand only if JS mode, else not needed. if (!$this->running_javascript()) { return; } // We already know that we waited for the DOM and the JS to be loaded, even the editor // so, we will use the reduced timeout as it is a common task and we should save time. try { $this->wait_for_pending_js(); // Expand all fieldsets link - which will only be there if there is more than one collapsible section. $expandallxpath = "//div[@class='collapsible-actions']" . "//a[contains(concat(' ', @class, ' '), ' collapsed ')]" . "//span[contains(concat(' ', @class, ' '), ' expandall ')]"; // Else, look for the first expand fieldset link (old theme structure). $expandsectionold = "//legend[@class='ftoggler']" . "//a[contains(concat(' ', @class, ' '), ' icons-collapse-expand ') and @aria-expanded = 'false']"; // Else, look for the first expand fieldset link (current theme structure). $expandsectioncurrent = "//fieldset//div[contains(concat(' ', @class, ' '), ' ftoggler ')]" . "//a[contains(concat(' ', @class, ' '), ' icons-collapse-expand ') and @aria-expanded = 'false']"; $collapseexpandlink = $this->find('xpath', $expandallxpath . '|' . $expandsectionold . '|' . $expandsectioncurrent, false, false, behat_base::get_reduced_timeout()); $collapseexpandlink->click(); $this->wait_for_pending_js(); } catch (ElementNotFoundException $e) { // The behat_base::find() method throws an exception if there are no elements, // we should not fail a test because of this. We continue if there are not expandable fields. } // Different try & catch as we can have expanded fieldsets with advanced fields on them. try { // Expand all fields xpath. $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" . "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]"; // We don't wait here as we already waited when getting the expand fieldsets links. if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) { return; } if ($this->getSession()->getDriver() instanceof \DMore\ChromeDriver\ChromeDriver) { // Chrome Driver produces unique xpaths for each element. foreach ($showmores as $showmore) { $showmore->click(); } } else { // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern // is added to the array with of xpaths with a [0], [1]... sufix, but when we click on an element it // does not matches the specified xpath anymore (now is a "Show less..." link) so [1] becomes [0], // that's why we always click on the first XPath match, will be always the next one. $iterations = count($showmores); for ($i = 0; $i < $iterations; $i++) { $showmores[0]->click(); } } } catch (ElementNotFoundException $e) { // We continue with the test. } } /** * Sets the field to wwwroot plus the given path. Include the first slash. * * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to local url "(?P<field_path_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $path * @return void */ public function i_set_the_field_to_local_url($field, $path) { global $CFG; $this->set_field_value($field, $CFG->wwwroot . $path); } /** * Sets the specified value to the field. * * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $value * @return void */ public function i_set_the_field_to($field, $value) { $this->set_field_value($field, $value); } /** * Sets the specified value to the field. * * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $containerelement Element we look in * @param string $containerselectortype The type of selector where we look in * @param string $value */ public function i_set_the_field_in_container_to($field, $containerelement, $containerselectortype, $value) { $this->set_field_value_in_container($field, $value, $containerselectortype, $containerelement); } /** * Press the key in the field to trigger the javascript keypress event * * Note that the character key will not actually be typed in the input field * * @Given /^I press key "(?P<key_string>(?:[^"]|\\")*)" in the field "(?P<field_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $key either char-code or character itself, * may optionally be prefixed with ctrl-, alt-, shift- or meta- * @param string $field * @return void */ public function i_press_key_in_the_field($key, $field) { if (!$this->running_javascript()) { throw new DriverException('Key press step is not available with Javascript disabled'); } $fld = behat_field_manager::get_form_field_from_label($field, $this); $modifier = null; $char = $key; if (preg_match('/-/', $key)) { list($modifier, $char) = preg_split('/-/', $key, 2); } if (is_numeric($char)) { $char = (int)$char; } $fld->key_press($char, $modifier); } /** * Sets the specified value to the field. * * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to multiline:$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param PyStringNode $value * @return void */ public function i_set_the_field_to_multiline($field, PyStringNode $value) { $this->set_field_value($field, (string)$value); } /** * Sets the specified value to the field with xpath. * * @Given /^I set the field with xpath "(?P<fieldxpath_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $value * @return void */ public function i_set_the_field_with_xpath_to($fieldxpath, $value) { $this->set_field_node_value($this->find('xpath', $fieldxpath), $value); } /** * Checks, the field matches the value. * * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $value * @return void */ public function the_field_matches_value($field, $value) { // Get the field. $formfield = behat_field_manager::get_form_field_from_label($field, $this); // Checks if the provided value matches the current field value. if (!$formfield->matches($value)) { $fieldvalue = $formfield->get_value(); throw new ExpectationException( 'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' , $this->getSession() ); } } /** * Checks, the field matches the value. * * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches multiline:$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param PyStringNode $value * @return void */ public function the_field_matches_multiline($field, PyStringNode $value) { $this->the_field_matches_value($field, (string)$value); } /** * Checks, the field does not match the value. * * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $value */ public function the_field_does_not_match_value($field, $value) { // Get the field. $formfield = behat_field_manager::get_form_field_from_label($field, $this); // Checks if the provided value matches the current field value. if ($formfield->matches($value)) { throw new ExpectationException( 'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' , $this->getSession() ); } } /** * Checks, the field matches the value. * * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $containerelement Element we look in * @param string $containerselectortype The type of selector where we look in * @param string $value */ public function the_field_in_container_matches_value($field, $containerelement, $containerselectortype, $value) { // Get the field. $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement); $formfield = behat_field_manager::get_form_field($node, $this->getSession()); // Checks if the provided value matches the current field value. if (!$formfield->matches($value)) { $fieldvalue = $formfield->get_value(); throw new ExpectationException( 'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' , $this->getSession() ); } } /** * Checks, the field does not match the value. * * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $field * @param string $containerelement Element we look in * @param string $containerselectortype The type of selector where we look in * @param string $value */ public function the_field_in_container_does_not_match_value($field, $containerelement, $containerselectortype, $value) { // Get the field. $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement); $formfield = behat_field_manager::get_form_field($node, $this->getSession()); // Checks if the provided value matches the current field value. if ($formfield->matches($value)) { throw new ExpectationException( 'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' , $this->getSession() ); } } /** * Checks, the field matches the value. * * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $fieldxpath * @param string $value * @return void */ public function the_field_with_xpath_matches_value($fieldxpath, $value) { // Get the field. $fieldnode = $this->find('xpath', $fieldxpath); $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession()); // Checks if the provided value matches the current field value. if (!$formfield->matches($value)) { $fieldvalue = $formfield->get_value(); throw new ExpectationException( 'The \'' . $fieldxpath . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' , $this->getSession() ); } } /** * Checks, the field does not match the value. * * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $fieldxpath * @param string $value * @return void */ public function the_field_with_xpath_does_not_match_value($fieldxpath, $value) { // Get the field. $fieldnode = $this->find('xpath', $fieldxpath); $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession()); // Checks if the provided value matches the current field value. if ($formfield->matches($value)) { throw new ExpectationException( 'The \'' . $fieldxpath . '\' value matches \'' . $value . '\' and it should not match it' , $this->getSession() ); } } /** * Checks, the provided field/value matches. * * @Then /^the following fields match these values:$/ * @throws ExpectationException * @param TableNode $data Pairs of | field | value | */ public function the_following_fields_match_these_values(TableNode $data) { // Expand all fields in case we have. $this->expand_all_fields(); $datahash = $data->getRowsHash(); // The action depends on the field type. foreach ($datahash as $locator => $value) { $this->the_field_matches_value($locator, $value); } } /** * Checks that the provided field/value pairs don't match. * * @Then /^the following fields do not match these values:$/ * @throws ExpectationException * @param TableNode $data Pairs of | field | value | */ public function the_following_fields_do_not_match_these_values(TableNode $data) { // Expand all fields in case we have. $this->expand_all_fields(); $datahash = $data->getRowsHash(); // The action depends on the field type. foreach ($datahash as $locator => $value) { $this->the_field_does_not_match_value($locator, $value); } } /** * Checks, the provided field/value matches. * * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" match these values:$/ * @throws ExpectationException * @param string $containerelement Element we look in * @param string $containerselectortype The type of selector where we look in * @param TableNode $data Pairs of | field | value | */ public function the_following_fields_in_container_match_these_values( $containerelement, $containerselectortype, TableNode $data) { // Expand all fields in case we have. $this->expand_all_fields(); $datahash = $data->getRowsHash(); // The action depends on the field type. foreach ($datahash as $locator => $value) { $this->the_field_in_container_matches_value($locator, $containerelement, $containerselectortype, $value); } } /** * Checks that the provided field/value pairs don't match. * * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" do not match these values:$/ * @throws ExpectationException * @param string $containerelement Element we look in * @param string $containerselectortype The type of selector where we look in * @param TableNode $data Pairs of | field | value | */ public function the_following_fields_in_container_do_not_match_these_values( $containerelement, $containerselectortype, TableNode $data) { // Expand all fields in case we have. $this->expand_all_fields(); $datahash = $data->getRowsHash(); // The action depends on the field type. foreach ($datahash as $locator => $value) { $this->the_field_in_container_does_not_match_value($locator, $containerelement, $containerselectortype, $value); } } /** * Checks, that given select box contains the specified option. * * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $select The select element name * @param string $option The option text/value. Plain value or comma separated * values if multiple. Commas in multiple values escaped with backslash. */ public function the_select_box_should_contain($select, $option) { $selectnode = $this->find_field($select); $multiple = $selectnode->hasAttribute('multiple'); $optionsarr = array(); // Array of passed value/text options to test. if ($multiple) { // Can pass multiple comma separated, with valuable commas escaped with backslash. foreach (preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $option)) as $opt) { $optionsarr[] = trim($opt); } } else { // Only one option has been passed. $optionsarr[] = trim($option); } // Now get all the values and texts in the select. $options = $selectnode->findAll('xpath', '//option'); $values = array(); foreach ($options as $opt) { $values[trim($opt->getValue())] = trim($opt->getText()); } foreach ($optionsarr as $opt) { // Verify every option is a valid text or value. if (!in_array($opt, $values) && !array_key_exists($opt, $values)) { throw new ExpectationException( 'The select box "' . $select . '" does not contain the option "' . $opt . '"', $this->getSession() ); } } } /** * Checks, that given select box does not contain the specified option. * * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param string $select The select element name * @param string $option The option text/value. Plain value or comma separated * values if multiple. Commas in multiple values escaped with backslash. */ public function the_select_box_should_not_contain($select, $option) { $selectnode = $this->find_field($select); $multiple = $selectnode->hasAttribute('multiple'); $optionsarr = array(); // Array of passed value/text options to test. if ($multiple) { // Can pass multiple comma separated, with valuable commas escaped with backslash. foreach (preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $option)) as $opt) { $optionsarr[] = trim($opt); } } else { // Only one option has been passed. $optionsarr[] = trim($option); } // Now get all the values and texts in the select. $options = $selectnode->findAll('xpath', '//option'); $values = array(); foreach ($options as $opt) { $values[trim($opt->getValue())] = trim($opt->getText()); } foreach ($optionsarr as $opt) { // Verify every option is not a valid text or value. if (in_array($opt, $values) || array_key_exists($opt, $values)) { throw new ExpectationException( 'The select box "' . $select . '" contains the option "' . $opt . '"', $this->getSession() ); } } } /** * Generic field setter. * * Internal API method, a generic *I set "VALUE" to "FIELD" field* * could be created based on it. * * @param string $fieldlocator The pointer to the field, it will depend on the field type. * @param string $value * @return void */ protected function set_field_value($fieldlocator, $value) { // We delegate to behat_form_field class, it will // guess the type properly as it is a select tag. $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this); $field->set_value($value); } /** * Generic field setter to be used by chainable steps. * * @param NodeElement $fieldnode * @param string $value */ public function set_field_node_value(NodeElement $fieldnode, string $value): void { $field = behat_field_manager::get_form_field($fieldnode, $this->getSession()); $field->set_value($value); } /** * Generic field setter. * * Internal API method, a generic *I set "VALUE" to "FIELD" field* * could be created based on it. * * @param string $fieldlocator The pointer to the field, it will depend on the field type. * @param string $value the value to set * @param string $containerselectortype The type of selector where we look in * @param string $containerelement Element we look in */ protected function set_field_value_in_container($fieldlocator, $value, $containerselectortype, $containerelement) { $node = $this->get_node_in_container('field', $fieldlocator, $containerselectortype, $containerelement); $this->set_field_node_value($node, $value); } /** * Select a value from single select and redirect. * * @Given /^I select "(?P<singleselect_option_string>(?:[^"]|\\")*)" from the "(?P<singleselect_name_string>(?:[^"]|\\")*)" singleselect$/ */ public function i_select_from_the_singleselect($option, $singleselect) { $this->execute('behat_forms::i_set_the_field_to', array($this->escape($singleselect), $this->escape($option))); if (!$this->running_javascript()) { // Press button in the specified select container. $containerxpath = "//div[" . "(contains(concat(' ', normalize-space(@class), ' '), ' singleselect ') " . "or contains(concat(' ', normalize-space(@class), ' '), ' urlselect ')". ") and ( .//label[contains(normalize-space(string(.)), '" . $singleselect . "')] " . "or .//select[(./@name='" . $singleselect . "' or ./@id='". $singleselect . "')]" . ")]"; $this->execute('behat_general::i_click_on_in_the', array(get_string('go'), "button", $containerxpath, "xpath_element") ); } } /** * Select item from autocomplete list. * * @Given /^I click on "([^"]*)" item in the autocomplete list$/ * * @param string $item */ public function i_click_on_item_in_the_autocomplete_list($item) { $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//*[contains(concat('|', string(.), '|'),'|" . $item . "|')]"; $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']); } /** * Open the auto-complete suggestions list (Assuming there is only one on the page.). * * @Given I open the autocomplete suggestions list * @Given I open the autocomplete suggestions list in the :container :containertype */ public function i_open_the_autocomplete_suggestions_list($container = null, $containertype = null) { $csstarget = ".form-autocomplete-downarrow"; if ($container && $containertype) { $this->execute('behat_general::i_click_on_in_the', [$csstarget, 'css_element', $container, $containertype]); } else { $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']); } } /** * Expand the given autocomplete list * * @Given /^I expand the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$/ * * @param string $field Field name */ public function i_expand_the_autocomplete($field) { $csstarget = '.form-autocomplete-downarrow'; $node = $this->get_node_in_container('css_element', $csstarget, 'form_row', $field); $this->ensure_node_is_visible($node); $node->click(); } /** * Assert the given option exist in the given autocomplete list * * @Given /^I should see "(?P<option_string>(?:[^"]|\\")*)" in the list of options for the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$$/ * * @param string $option Name of option * @param string $field Field name */ public function i_should_see_in_the_list_of_option_for_the_autocomplete($option, $field) { $xpathtarget = "//div[contains(@class, 'form-autocomplete-selection') and contains(.//div, '" . $option . "')]"; $node = $this->get_node_in_container('xpath_element', $xpathtarget, 'form_row', $field); $this->ensure_node_is_visible($node); } /** * Checks whether the select menu contains an option with specified text or not. * * @Then the :name select menu should contain :option * @Then the :name select menu should :not contain :option * * @throws ExpectationException When the expectation is not satisfied * @param string $label The label of the select menu element * @param string $option The string that is used to identify an option within the select menu. If the string * has two items separated by '>' (ex. "Group > Option"), the first item ("Group") will be * used to identify a particular group within the select menu, while the second ("Option") * will be used to identify an option within that group. Otherwise, a string with a single * item (ex. "Option") will be used to identify an option within the select menu regardless * of any existing groups. * @param string|null $not If set, the select menu should not contain the specified option. If null, the option * should be present. */ public function the_select_menu_should_contain(string $label, string $option, ?string $not = null) { $field = behat_field_manager::get_form_field_from_label($label, $this); if (!method_exists($field, 'has_option')) { throw new coding_exception('Field does not support the has_option function.'); } // If the select menu contains the specified option but it should not. if ($field->has_option($option) && $not) { throw new ExpectationException( "The select menu should not contain \"{$option}\" but it does.", $this->getSession() ); } // If the select menu does not contain the specified option but it should. if (!$field->has_option($option) && !$not) { throw new ExpectationException( "The select menu should contain \"{$option}\" but it does not.", $this->getSession() ); } } } behat_general.php 0000644 00000273007 15151222201 0010035 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * General use steps definitions. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Gherkin\Node\TableNode; use Behat\Mink\Element\NodeElement; use Behat\Mink\Exception\DriverException; use Behat\Mink\Exception\ElementNotFoundException; use Behat\Mink\Exception\ExpectationException; use Facebook\WebDriver\Exception\NoSuchElementException; use Facebook\WebDriver\Exception\StaleElementReferenceException; /** * Cross component steps definitions. * * Basic web application definitions from MinkExtension and * BehatchExtension. Definitions modified according to our needs * when necessary and including only the ones we need to avoid * overlapping and confusion. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_general extends behat_base { /** * @var string used by {@link switch_to_window()} and * {@link switch_to_the_main_window()} to work-around a Chrome browser issue. */ const MAIN_WINDOW_NAME = '__moodle_behat_main_window_name'; /** * @var string when we want to check whether or not a new page has loaded, * we first write this unique string into the page. Then later, by checking * whether it is still there, we can tell if a new page has been loaded. */ const PAGE_LOAD_DETECTION_STRING = 'new_page_not_loaded_since_behat_started_watching'; /** * @var $pageloaddetectionrunning boolean Used to ensure that page load detection was started before a page reload * was checked for. */ private $pageloaddetectionrunning = false; /** * Opens Moodle homepage. * * @Given /^I am on homepage$/ */ public function i_am_on_homepage() { $this->execute('behat_general::i_visit', ['/']); } /** * Opens Moodle site homepage. * * @Given /^I am on site homepage$/ */ public function i_am_on_site_homepage() { $this->execute('behat_general::i_visit', ['/?redirect=0']); } /** * Opens course index page. * * @Given /^I am on course index$/ */ public function i_am_on_course_index() { $this->execute('behat_general::i_visit', ['/course/index.php']); } /** * Reloads the current page. * * @Given /^I reload the page$/ */ public function reload() { $this->getSession()->reload(); } /** * Follows the page redirection. Use this step after any action that shows a message and waits for a redirection * * @Given /^I wait to be redirected$/ */ public function i_wait_to_be_redirected() { // Xpath and processes based on core_renderer::redirect_message(), core_renderer::$metarefreshtag and // moodle_page::$periodicrefreshdelay possible values. if (!$metarefresh = $this->getSession()->getPage()->find('xpath', "//head/descendant::meta[@http-equiv='refresh']")) { // We don't fail the scenario if no redirection with message is found to avoid race condition false failures. return true; } // Wrapped in try & catch in case the redirection has already been executed. try { $content = $metarefresh->getAttribute('content'); } catch (NoSuchElementException $e) { return true; } catch (StaleElementReferenceException $e) { return true; } // Getting the refresh time and the url if present. if (strstr($content, 'url') != false) { list($waittime, $url) = explode(';', $content); // Cleaning the URL value. $url = trim(substr($url, strpos($url, 'http'))); } else { // Just wait then. $waittime = $content; } // Wait until the URL change is executed. if ($this->running_javascript()) { $this->getSession()->wait($waittime * 1000); } else if (!empty($url)) { // We redirect directly as we can not wait for an automatic redirection. $this->getSession()->getDriver()->getClient()->request('get', $url); } else { // Reload the page if no URL was provided. $this->getSession()->getDriver()->reload(); } } /** * Switches to the specified iframe. * * @Given /^I switch to "(?P<iframe_name_string>(?:[^"]|\\")*)" iframe$/ * @Given /^I switch to "(?P<iframe_name_string>(?:[^"]|\\")*)" class iframe$/ * @param string $name The name of the iframe */ public function switch_to_iframe($name) { // We spin to give time to the iframe to be loaded. // Using extended timeout as we don't know about which // kind of iframe will be loaded. $this->spin( function($context) use ($name){ $iframe = $context->find('iframe', $name); if ($iframe->hasAttribute('name')) { $iframename = $iframe->getAttribute('name'); } else { if (!$this->running_javascript()) { throw new \coding_exception('iframe must have a name attribute to use the switchTo command.'); } $iframename = uniqid(); $this->execute_js_on_node($iframe, "{{ELEMENT}}.name = '{$iframename}';"); } $context->getSession()->switchToIFrame($iframename); // If no exception we are done. return true; }, behat_base::get_extended_timeout() ); } /** * Switches to the main Moodle frame. * * @Given /^I switch to the main frame$/ */ public function switch_to_the_main_frame() { $this->getSession()->switchToIFrame(); } /** * Switches to the specified window. Useful when interacting with popup windows. * * @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/ * @param string $windowname */ public function switch_to_window($windowname) { if ($windowname === self::MAIN_WINDOW_NAME) { // When switching to the main window normalise the window name to null. // This is normalised further in the Mink driver to the root window ID. $windowname = null; } $this->getSession()->switchToWindow($windowname); } /** * Switches to a second window. * * @Given /^I switch to a second window$/ * @throws DriverException If there aren't exactly 2 windows open. */ public function switch_to_second_window() { $names = $this->getSession()->getWindowNames(); if (count($names) !== 2) { throw new DriverException('Expected to see 2 windows open, found ' . count($names)); } $this->getSession()->switchToWindow($names[1]); } /** * Switches to the main Moodle window. Useful when you finish interacting with popup windows. * * @Given /^I switch to the main window$/ */ public function switch_to_the_main_window() { $this->switch_to_window(self::MAIN_WINDOW_NAME); } /** * Closes all extra windows opened during the navigation. * * This assumes all popups are opened by the main tab and you will now get back. * * @Given /^I close all opened windows$/ * @throws DriverException If there aren't exactly 1 tabs open when finish or no javascript running */ public function i_close_all_opened_windows() { if (!$this->running_javascript()) { throw new DriverException('Closing windows steps require javascript'); } $names = $this->getSession()->getWindowNames(); for ($index = 1; $index < count($names); $index ++) { $this->getSession()->switchToWindow($names[$index]); $this->execute_script("window.open('', '_self').close();"); } $names = $this->getSession()->getWindowNames(); if (count($names) !== 1) { throw new DriverException('Expected to see 1 tabs open, not ' . count($names)); } $this->getSession()->switchToWindow($names[0]); } /** * Accepts the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental. * @Given /^I accept the currently displayed dialog$/ */ public function accept_currently_displayed_alert_dialog() { $this->getSession()->getDriver()->getWebDriver()->switchTo()->alert()->accept(); } /** * Dismisses the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental. * @Given /^I dismiss the currently displayed dialog$/ */ public function dismiss_currently_displayed_alert_dialog() { $this->getSession()->getDriver()->getWebDriver()->switchTo()->alert()->dismiss(); } /** * Clicks link with specified id|title|alt|text. * * @When /^I follow "(?P<link_string>(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $link */ public function click_link($link) { $linknode = $this->find_link($link); $this->ensure_node_is_visible($linknode); $linknode->click(); } /** * Waits X seconds. Required after an action that requires data from an AJAX request. * * @Then /^I wait "(?P<seconds_number>\d+)" seconds$/ * @param int $seconds */ public function i_wait_seconds($seconds) { if ($this->running_javascript()) { $this->getSession()->wait($seconds * 1000); } else { sleep($seconds); } } /** * Waits until the page is completely loaded. This step is auto-executed after every step. * * @Given /^I wait until the page is ready$/ */ public function wait_until_the_page_is_ready() { // No need to wait if not running JS. if (!$this->running_javascript()) { return; } $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS); } /** * Waits until the provided element selector exists in the DOM * * Using the protected method as this method will be usually * called by other methods which are not returning a set of * steps and performs the actions directly, so it would not * be executed if it returns another step. * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" exists$/ * @param string $element * @param string $selector * @return void */ public function wait_until_exists($element, $selectortype) { $this->ensure_element_exists($element, $selectortype); } /** * Waits until the provided element does not exist in the DOM * * Using the protected method as this method will be usually * called by other methods which are not returning a set of * steps and performs the actions directly, so it would not * be executed if it returns another step. * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" does not exist$/ * @param string $element * @param string $selector * @return void */ public function wait_until_does_not_exists($element, $selectortype) { $this->ensure_element_does_not_exist($element, $selectortype); } /** * Generic mouse over action. Mouse over a element of the specified type. * * @When /^I hover "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ * @param string $element Element we look for * @param string $selectortype The type of what we look for */ public function i_hover($element, $selectortype) { // Gets the node based on the requested selector type and locator. $node = $this->get_selected_node($selectortype, $element); $this->execute_js_on_node($node, '{{ELEMENT}}.scrollIntoView();'); $node->mouseOver(); } /** * Generic mouse over action. Mouse over a element of the specified type. * * @When /^I hover over the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*) in the "(?P<container_element_string>(?:[^"]|\\")*)" "(?P<container_selector_string>[^"]*)"$/ * @param string $element Element we look for * @param string $selectortype The type of what we look for * @param string $containerelement Element we look for * @param string $containerselectortype The type of what we look for */ public function i_hover_in_the(string $element, $selectortype, string $containerelement, $containerselectortype): void { // Gets the node based on the requested selector type and locator. $node = $this->get_node_in_container($selectortype, $element, $containerselectortype, $containerselectortype); $this->execute_js_on_node($node, '{{ELEMENT}}.scrollIntoView();'); $node->mouseOver(); } /** * Generic click action. Click on the element of the specified type. * * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ * @param string $element Element we look for * @param string $selectortype The type of what we look for */ public function i_click_on($element, $selectortype) { // Gets the node based on the requested selector type and locator. $node = $this->get_selected_node($selectortype, $element); $this->ensure_node_is_visible($node); $node->click(); } /** * Sets the focus and takes away the focus from an element, generating blur JS event. * * @When /^I take focus off "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ * @param string $element Element we look for * @param string $selectortype The type of what we look for */ public function i_take_focus_off_field($element, $selectortype) { if (!$this->running_javascript()) { throw new ExpectationException('Can\'t take focus off from "' . $element . '" in non-js mode', $this->getSession()); } // Gets the node based on the requested selector type and locator. $node = $this->get_selected_node($selectortype, $element); $this->ensure_node_is_visible($node); // Ensure element is focused before taking it off. $node->focus(); $node->blur(); } /** * Clicks the specified element and confirms the expected dialogue. * * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" confirming the dialogue$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $element Element we look for * @param string $selectortype The type of what we look for */ public function i_click_on_confirming_the_dialogue($element, $selectortype) { $this->i_click_on($element, $selectortype); $this->execute('behat_general::accept_currently_displayed_alert_dialog', []); $this->wait_until_the_page_is_ready(); } /** * Clicks the specified element and dismissing the expected dialogue. * * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" dismissing the dialogue$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $element Element we look for * @param string $selectortype The type of what we look for */ public function i_click_on_dismissing_the_dialogue($element, $selectortype) { $this->i_click_on($element, $selectortype); $this->execute('behat_general::dismiss_currently_displayed_alert_dialog', []); $this->wait_until_the_page_is_ready(); } /** * Click on the element of the specified type which is located inside the second element. * * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ * @param string $element Element we look for * @param string $selectortype The type of what we look for * @param string $nodeelement Element we look in * @param string $nodeselectortype The type of selector where we look in */ public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) { $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); $this->ensure_node_is_visible($node); $node->click(); } /** * Drags and drops the specified element to the specified container. This step does not work in all the browsers, consider it experimental. * * The steps definitions calling this step as part of them should * manage the wait times by themselves as the times and when the * waits should be done depends on what is being dragged & dropper. * * @Given /^I drag "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" and I drop it in "(?P<container_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ * @param string $element * @param string $selectortype * @param string $containerelement * @param string $containerselectortype */ public function i_drag_and_i_drop_it_in($source, $sourcetype, $target, $targettype) { if (!$this->running_javascript()) { throw new DriverException('Drag and drop steps require javascript'); } $source = $this->find($sourcetype, $source); $target = $this->find($targettype, $target); if (!$source->isVisible()) { throw new ExpectationException("'{$source}' '{$sourcetype}' is not visible", $this->getSession()); } if (!$target->isVisible()) { throw new ExpectationException("'{$target}' '{$targettype}' is not visible", $this->getSession()); } $this->getSession()->getDriver()->dragTo($source->getXpath(), $target->getXpath()); } /** * Checks, that the specified element is visible. Only available in tests using Javascript. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should be visible$/ * @throws ElementNotFoundException * @throws ExpectationException * @throws DriverException * @param string $element * @param string $selectortype * @return void */ public function should_be_visible($element, $selectortype) { if (!$this->running_javascript()) { throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); } $node = $this->get_selected_node($selectortype, $element); if (!$node->isVisible()) { throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is not visible', $this->getSession()); } } /** * Checks, that the existing element is not visible. Only available in tests using Javascript. * * As a "not" method, it's performance could not be good, but in this * case the performance is good because the element must exist, * otherwise there would be a ElementNotFoundException, also here we are * not spinning until the element is visible. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/ * @throws ElementNotFoundException * @throws ExpectationException * @param string $element * @param string $selectortype * @return void */ public function should_not_be_visible($element, $selectortype) { try { $this->should_be_visible($element, $selectortype); } catch (ExpectationException $e) { // All as expected. return; } throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is visible', $this->getSession()); } /** * Checks, that the specified element is visible inside the specified container. Only available in tests using Javascript. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should be visible$/ * @throws ElementNotFoundException * @throws DriverException * @throws ExpectationException * @param string $element Element we look for * @param string $selectortype The type of what we look for * @param string $nodeelement Element we look in * @param string $nodeselectortype The type of selector where we look in */ public function in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { if (!$this->running_javascript()) { throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); } $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); if (!$node->isVisible()) { throw new ExpectationException( '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is not visible', $this->getSession() ); } } /** * Checks, that the existing element is not visible inside the existing container. Only available in tests using Javascript. * * As a "not" method, it's performance could not be good, but in this * case the performance is good because the element must exist, * otherwise there would be a ElementNotFoundException, also here we are * not spinning until the element is visible. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should not be visible$/ * @throws ElementNotFoundException * @throws ExpectationException * @param string $element Element we look for * @param string $selectortype The type of what we look for * @param string $nodeelement Element we look in * @param string $nodeselectortype The type of selector where we look in */ public function in_the_should_not_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { try { $this->in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype); } catch (ExpectationException $e) { // All as expected. return; } throw new ExpectationException( '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is visible', $this->getSession() ); } /** * Checks, that page contains specified text. It also checks if the text is visible when running Javascript tests. * * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @param string $text */ public function assert_page_contains_text($text) { // Looking for all the matching nodes without any other descendant matching the // same xpath (we are using contains(., ....). $xpathliteral = behat_context_helper::escape($text); $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; try { $nodes = $this->find_all('xpath', $xpath); } catch (ElementNotFoundException $e) { throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession()); } // If we are not running javascript we have enough with the // element existing as we can't check if it is visible. if (!$this->running_javascript()) { return; } // We spin as we don't have enough checking that the element is there, we // should also ensure that the element is visible. Using microsleep as this // is a repeated step and global performance is important. $this->spin( function($context, $args) { foreach ($args['nodes'] as $node) { if ($node->isVisible()) { return true; } } // If non of the nodes is visible we loop again. throw new ExpectationException('"' . $args['text'] . '" text was found but was not visible', $context->getSession()); }, array('nodes' => $nodes, 'text' => $text), false, false, true ); } /** * Checks, that page doesn't contain specified text. When running Javascript tests it also considers that texts may be hidden. * * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @param string $text */ public function assert_page_not_contains_text($text) { // Looking for all the matching nodes without any other descendant matching the // same xpath (we are using contains(., ....). $xpathliteral = behat_context_helper::escape($text); $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; // We should wait a while to ensure that the page is not still loading elements. // Waiting less than self::get_timeout() as we already waited for the DOM to be ready and // all JS to be executed. try { $nodes = $this->find_all('xpath', $xpath, false, false, self::get_reduced_timeout()); } catch (ElementNotFoundException $e) { // All ok. return; } // If we are not running javascript we have enough with the // element existing as we can't check if it is hidden. if (!$this->running_javascript()) { throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession()); } // If the element is there we should be sure that it is not visible. $this->spin( function($context, $args) { foreach ($args['nodes'] as $node) { // If element is removed from dom, then just exit. try { // If element is visible then throw exception, so we keep spinning. if ($node->isVisible()) { throw new ExpectationException('"' . $args['text'] . '" text was found in the page', $context->getSession()); } } catch (NoSuchElementException $e) { // Do nothing just return, as element is no more on page. return true; } catch (ElementNotFoundException $e) { // Do nothing just return, as element is no more on page. return true; } } // If non of the found nodes is visible we consider that the text is not visible. return true; }, array('nodes' => $nodes, 'text' => $text), behat_base::get_reduced_timeout(), false, true ); } /** * Checks, that the specified element contains the specified text. When running Javascript tests it also considers that texts may be hidden. * * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ * @throws ElementNotFoundException * @throws ExpectationException * @param string $text * @param string $element Element we look in. * @param string $selectortype The type of element where we are looking in. */ public function assert_element_contains_text($text, $element, $selectortype) { // Getting the container where the text should be found. $container = $this->get_selected_node($selectortype, $element); // Looking for all the matching nodes without any other descendant matching the // same xpath (we are using contains(., ....). $xpathliteral = behat_context_helper::escape($text); $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; // Wait until it finds the text inside the container, otherwise custom exception. try { $nodes = $this->find_all('xpath', $xpath, false, $container); } catch (ElementNotFoundException $e) { throw new ExpectationException('"' . $text . '" text was not found in the "' . $element . '" element', $this->getSession()); } // If we are not running javascript we have enough with the // element existing as we can't check if it is visible. if (!$this->running_javascript()) { return; } // We also check the element visibility when running JS tests. Using microsleep as this // is a repeated step and global performance is important. $this->spin( function($context, $args) { foreach ($args['nodes'] as $node) { if ($node->isVisible()) { return true; } } throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element but was not visible', $context->getSession()); }, array('nodes' => $nodes, 'text' => $text, 'element' => $element), false, false, true ); } /** * Checks, that the specified element does not contain the specified text. When running Javascript tests it also considers that texts may be hidden. * * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ * @throws ElementNotFoundException * @throws ExpectationException * @param string $text * @param string $element Element we look in. * @param string $selectortype The type of element where we are looking in. */ public function assert_element_not_contains_text($text, $element, $selectortype) { // Getting the container where the text should be found. $container = $this->get_selected_node($selectortype, $element); // Looking for all the matching nodes without any other descendant matching the // same xpath (we are using contains(., ....). $xpathliteral = behat_context_helper::escape($text); $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; // We should wait a while to ensure that the page is not still loading elements. // Giving preference to the reliability of the results rather than to the performance. try { $nodes = $this->find_all('xpath', $xpath, false, $container, self::get_reduced_timeout()); } catch (ElementNotFoundException $e) { // All ok. return; } // If we are not running javascript we have enough with the // element not being found as we can't check if it is visible. if (!$this->running_javascript()) { throw new ExpectationException('"' . $text . '" text was found in the "' . $element . '" element', $this->getSession()); } // We need to ensure all the found nodes are hidden. $this->spin( function($context, $args) { foreach ($args['nodes'] as $node) { if ($node->isVisible()) { throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element', $context->getSession()); } } // If all the found nodes are hidden we are happy. return true; }, array('nodes' => $nodes, 'text' => $text, 'element' => $element), behat_base::get_reduced_timeout(), false, true ); } /** * Checks, that the first specified element appears before the second one. * * @Then :preelement :preselectortype should appear before :postelement :postselectortype * @Then :preelement :preselectortype should appear before :postelement :postselectortype in the :containerelement :containerselectortype * @throws ExpectationException * @param string $preelement The locator of the preceding element * @param string $preselectortype The selector type of the preceding element * @param string $postelement The locator of the latest element * @param string $postselectortype The selector type of the latest element * @param string $containerelement * @param string $containerselectortype */ public function should_appear_before( string $preelement, string $preselectortype, string $postelement, string $postselectortype, ?string $containerelement = null, ?string $containerselectortype = null ) { $msg = "'{$preelement}' '{$preselectortype}' does not appear before '{$postelement}' '{$postselectortype}'"; $this->check_element_order( $containerelement, $containerselectortype, $preelement, $preselectortype, $postelement, $postselectortype, $msg ); } /** * Checks, that the first specified element appears after the second one. * * @Then :postelement :postselectortype should appear after :preelement :preselectortype * @Then :postelement :postselectortype should appear after :preelement :preselectortype in the :containerelement :containerselectortype * @throws ExpectationException * @param string $postelement The locator of the latest element * @param string $postselectortype The selector type of the latest element * @param string $preelement The locator of the preceding element * @param string $preselectortype The selector type of the preceding element * @param string $containerelement * @param string $containerselectortype */ public function should_appear_after( string $postelement, string $postselectortype, string $preelement, string $preselectortype, ?string $containerelement = null, ?string $containerselectortype = null ) { $msg = "'{$postelement}' '{$postselectortype}' does not appear after '{$preelement}' '{$preselectortype}'"; $this->check_element_order( $containerelement, $containerselectortype, $preelement, $preselectortype, $postelement, $postselectortype, $msg ); } /** * Shared code to check whether an element is before or after another one. * * @param string $containerelement * @param string $containerselectortype * @param string $preelement The locator of the preceding element * @param string $preselectortype The locator of the preceding element * @param string $postelement The locator of the following element * @param string $postselectortype The selector type of the following element * @param string $msg Message to output if this fails */ protected function check_element_order( ?string $containerelement, ?string $containerselectortype, string $preelement, string $preselectortype, string $postelement, string $postselectortype, string $msg ) { $containernode = false; if ($containerselectortype && $containerelement) { // Get the container node. $containernode = $this->get_selected_node($containerselectortype, $containerelement); $msg .= " in the '{$containerelement}' '{$containerselectortype}'"; } list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); $newlines = [ "\r\n", "\r", "\n", ]; $prexpath = str_replace($newlines, ' ', $this->find($preselector, $prelocator, false, $containernode)->getXpath()); $postxpath = str_replace($newlines, ' ', $this->find($postselector, $postlocator, false, $containernode)->getXpath()); if ($this->running_javascript()) { // The xpath to do this was running really slowly on certain Chrome versions so we are using // this DOM method instead. $js = <<<EOF (function() { var a = document.evaluate("{$prexpath}", document, null, XPathResult.ANY_TYPE, null).iterateNext(); var b = document.evaluate("{$postxpath}", document, null, XPathResult.ANY_TYPE, null).iterateNext(); return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING; })() EOF; $ok = $this->evaluate_script($js); } else { // Using following xpath axe to find it. $xpath = "{$prexpath}/following::*[contains(., {$postxpath})]"; $ok = $this->getSession()->getDriver()->find($xpath); } if (!$ok) { throw new ExpectationException($msg, $this->getSession()); } } /** * Checks, that element of specified type is disabled. * * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be disabled$/ * @throws ExpectationException Thrown by behat_base::find * @param string $element Element we look in * @param string $selectortype The type of element where we are looking in. */ public function the_element_should_be_disabled($element, $selectortype) { $this->the_attribute_of_should_be_set("disabled", $element, $selectortype, false); } /** * Checks, that element of specified type is enabled. * * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be enabled$/ * @throws ExpectationException Thrown by behat_base::find * @param string $element Element we look on * @param string $selectortype The type of where we look */ public function the_element_should_be_enabled($element, $selectortype) { $this->the_attribute_of_should_be_set("disabled", $element, $selectortype, true); } /** * Checks the provided element and selector type are readonly on the current page. * * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be readonly$/ * @throws ExpectationException Thrown by behat_base::find * @param string $element Element we look in * @param string $selectortype The type of element where we are looking in. */ public function the_element_should_be_readonly($element, $selectortype) { $this->the_attribute_of_should_be_set("readonly", $element, $selectortype, false); } /** * Checks the provided element and selector type are not readonly on the current page. * * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not be readonly$/ * @throws ExpectationException Thrown by behat_base::find * @param string $element Element we look in * @param string $selectortype The type of element where we are looking in. */ public function the_element_should_not_be_readonly($element, $selectortype) { $this->the_attribute_of_should_be_set("readonly", $element, $selectortype, true); } /** * Checks the provided element and selector type exists in the current page. * * This step is for advanced users, use it if you don't find anything else suitable for what you need. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $element The locator of the specified selector * @param string $selectortype The selector type */ public function should_exist($element, $selectortype) { // Will throw an ElementNotFoundException if it does not exist. $this->find($selectortype, $element); } /** * Checks that the provided element and selector type not exists in the current page. * * This step is for advanced users, use it if you don't find anything else suitable for what you need. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist$/ * @throws ExpectationException * @param string $element The locator of the specified selector * @param string $selectortype The selector type */ public function should_not_exist($element, $selectortype) { // Will throw an ElementNotFoundException if it does not exist, but, actually it should not exist, so we try & // catch it. try { // The exception does not really matter as we will catch it and will never "explode". $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $element); // Using the spin method as we want a reduced timeout but there is no need for a 0.1 seconds interval // because in the optimistic case we will timeout. // If all goes good it will throw an ElementNotFoundExceptionn that we will catch. $this->find($selectortype, $element, $exception, false, behat_base::get_reduced_timeout()); } catch (ElementNotFoundException $e) { // We expect the element to not be found. return; } // The element was found and should not have been. Throw an exception. throw new ExpectationException("The '{$element}' '{$selectortype}' exists in the current page", $this->getSession()); } /** * Ensure that edit mode is (not) available on the current page. * * @Then edit mode should be available on the current page * @Then edit mode should :not be available on the current page * @param bool $not */ public function edit_mode_should_be_available(bool $not = false): void { $isavailable = $this->is_edit_mode_available(); $shouldbeavailable = empty($not); if ($isavailable && !$shouldbeavailable) { throw new ExpectationException("Edit mode is available and should not be", $this->getSession()); } else if ($shouldbeavailable && !$isavailable) { throw new ExpectationException("Edit mode is not available and should be", $this->getSession()); } } /** * Check whether edit mode is available on the current page. * * @return bool */ public function is_edit_mode_available(): bool { // If the course is already in editing mode then it will have the class 'editing' on the body. // This is a 'cheap' way of telling if the course is in editing mode and therefore if edit mode is available. $body = $this->find('css', 'body'); if ($body->hasClass('editing')) { return true; } try { $this->find('field', get_string('editmode'), false, false, 0); return true; } catch (ElementNotFoundException $e) { return false; } } /** * This step triggers cron like a user would do going to admin/cron.php. * * @Given /^I trigger cron$/ */ public function i_trigger_cron() { $this->execute('behat_general::i_visit', ['/admin/cron.php']); } /** * Runs a scheduled task immediately, given full class name. * * This is faster and more reliable than running cron (running cron won't * work more than once in the same test, for instance). However it is * a little less 'realistic'. * * While the task is running, we suppress mtrace output because it makes * the Behat result look ugly. * * Note: Most of the code relating to running a task is based on * admin/cli/scheduled_task.php. * * @Given /^I run the scheduled task "(?P<task_name>[^"]+)"$/ * @param string $taskname Name of task e.g. 'mod_whatever\task\do_something' */ public function i_run_the_scheduled_task($taskname) { global $CFG; require_once("{$CFG->libdir}/cronlib.php"); $task = \core\task\manager::get_scheduled_task($taskname); if (!$task) { throw new DriverException('The "' . $taskname . '" scheduled task does not exist'); } // Do setup for cron task. raise_memory_limit(MEMORY_EXTRA); cron_setup_user(); // Get lock. $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { throw new DriverException('Unable to obtain core_cron lock for scheduled task'); } if (!$lock = $cronlockfactory->get_lock('\\' . get_class($task), 10)) { $cronlock->release(); throw new DriverException('Unable to obtain task lock for scheduled task'); } $task->set_lock($lock); if (!$task->is_blocking()) { $cronlock->release(); } else { $task->set_cron_lock($cronlock); } try { // Prepare the renderer. cron_prepare_core_renderer(); // Discard task output as not appropriate for Behat output! ob_start(); $task->execute(); ob_end_clean(); // Restore the previous renderer. cron_prepare_core_renderer(true); // Mark task complete. \core\task\manager::scheduled_task_complete($task); } catch (Exception $e) { // Restore the previous renderer. cron_prepare_core_renderer(true); // Mark task failed and throw exception. \core\task\manager::scheduled_task_failed($task); throw new DriverException('The "' . $taskname . '" scheduled task failed', 0, $e); } } /** * Runs all ad-hoc tasks in the queue. * * This is faster and more reliable than running cron (running cron won't * work more than once in the same test, for instance). However it is * a little less 'realistic'. * * While the task is running, we suppress mtrace output because it makes * the Behat result look ugly. * * @Given /^I run all adhoc tasks$/ * @throws DriverException */ public function i_run_all_adhoc_tasks() { global $CFG, $DB; require_once("{$CFG->libdir}/cronlib.php"); // Do setup for cron task. cron_setup_user(); // Discard task output as not appropriate for Behat output! ob_start(); // Run all tasks which have a scheduled runtime of before now. $timenow = time(); while (!\core\task\manager::static_caches_cleared_since($timenow) && $task = \core\task\manager::get_next_adhoc_task($timenow)) { // Clean the output buffer between tasks. ob_clean(); // Run the task. cron_run_inner_adhoc_task($task); // Check whether the task record still exists. // If a task was successful it will be removed. // If it failed then it will still exist. if ($DB->record_exists('task_adhoc', ['id' => $task->get_id()])) { // End ouptut buffering and flush the current buffer. // This should be from just the current task. ob_end_flush(); throw new DriverException('An adhoc task failed', 0); } } ob_end_clean(); } /** * Checks that an element and selector type exists in another element and selector type on the current page. * * This step is for advanced users, use it if you don't find anything else suitable for what you need. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $element The locator of the specified selector * @param string $selectortype The selector type * @param NodeElement|string $containerelement The locator of the container selector * @param string $containerselectortype The container selector type */ public function should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { // Will throw an ElementNotFoundException if it does not exist. $this->get_node_in_container($selectortype, $element, $containerselectortype, $containerelement); } /** * Checks that an element and selector type does not exist in another element and selector type on the current page. * * This step is for advanced users, use it if you don't find anything else suitable for what you need. * * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ * @throws ExpectationException * @param string $element The locator of the specified selector * @param string $selectortype The selector type * @param NodeElement|string $containerelement The locator of the container selector * @param string $containerselectortype The container selector type */ public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { // Get the container node. $containernode = $this->find($containerselectortype, $containerelement); // Will throw an ElementNotFoundException if it does not exist, but, actually it should not exist, so we try & // catch it. try { // Looks for the requested node inside the container node. $this->find($selectortype, $element, false, $containernode, behat_base::get_reduced_timeout()); } catch (ElementNotFoundException $e) { // We expect the element to not be found. return; } // The element was found and should not have been. Throw an exception. $elementdescription = $this->get_selector_description($selectortype, $element); $containerdescription = $this->get_selector_description($containerselectortype, $containerelement); throw new ExpectationException( "The {$elementdescription} exists in the {$containerdescription}", $this->getSession() ); } /** * Change browser window size * * Allowed sizes: * - mobile: 425x750 * - tablet: 768x1024 * - small: 1024x768 * - medium: 1366x768 * - large: 2560x1600 * - custom: widthxheight * * Example: I change window size to "small" or I change window size to "1024x768" * or I change viewport size to "800x600". The viewport option is useful to guarantee that the * browser window has same viewport size even when you run Behat on multiple operating systems. * * @throws ExpectationException * @Then /^I change (window|viewport) size to "(mobile|tablet|small|medium|large|\d+x\d+)"$/ * @Then /^I change the (window|viewport) size to "(mobile|tablet|small|medium|large|\d+x\d+)"$/ * @param string $windowsize size of the window (mobile|tablet|small|medium|large|wxh). */ public function i_change_window_size_to($windowviewport, $windowsize) { $this->resize_window($windowsize, $windowviewport === 'viewport'); } /** * Checks whether there the specified attribute is set or not. * * @Then the :attribute attribute of :element :selectortype should be set * @Then the :attribute attribute of :element :selectortype should :not be set * * @throws ExpectationException * @param string $attribute Name of attribute * @param string $element The locator of the specified selector * @param string $selectortype The selector type * @param string $not */ public function the_attribute_of_should_be_set($attribute, $element, $selectortype, $not = null) { // Get the container node (exception if it doesn't exist). $containernode = $this->get_selected_node($selectortype, $element); $hasattribute = $containernode->hasAttribute($attribute); if ($not && $hasattribute) { $value = $containernode->getAttribute($attribute); // Should not be set but is. throw new ExpectationException( "The attribute \"{$attribute}\" should not be set but has a value of '{$value}'", $this->getSession() ); } else if (!$not && !$hasattribute) { // Should be set but is not. throw new ExpectationException( "The attribute \"{$attribute}\" should be set but is not", $this->getSession() ); } } /** * Checks whether there is an attribute on the given element that contains the specified text. * * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should contain "(?P<text_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @param string $attribute Name of attribute * @param string $element The locator of the specified selector * @param string $selectortype The selector type * @param string $text Expected substring */ public function the_attribute_of_should_contain($attribute, $element, $selectortype, $text) { // Get the container node (exception if it doesn't exist). $containernode = $this->get_selected_node($selectortype, $element); $value = $containernode->getAttribute($attribute); if ($value == null) { throw new ExpectationException('The attribute "' . $attribute. '" does not exist', $this->getSession()); } else if (strpos($value, $text) === false) { throw new ExpectationException('The attribute "' . $attribute . '" does not contain "' . $text . '" (actual value: "' . $value . '")', $this->getSession()); } } /** * Checks that the attribute on the given element does not contain the specified text. * * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not contain "(?P<text_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @param string $attribute Name of attribute * @param string $element The locator of the specified selector * @param string $selectortype The selector type * @param string $text Expected substring */ public function the_attribute_of_should_not_contain($attribute, $element, $selectortype, $text) { // Get the container node (exception if it doesn't exist). $containernode = $this->get_selected_node($selectortype, $element); $value = $containernode->getAttribute($attribute); if ($value == null) { throw new ExpectationException('The attribute "' . $attribute. '" does not exist', $this->getSession()); } else if (strpos($value, $text) !== false) { throw new ExpectationException('The attribute "' . $attribute . '" contains "' . $text . '" (value: "' . $value . '")', $this->getSession()); } } /** * Checks the provided value exists in specific row/column of table. * * @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should contain "(?P<value_string>[^"]*)"$/ * @throws ElementNotFoundException * @param string $row row text which will be looked in. * @param string $column column text to search (or numeric value for the column position) * @param string $table table id/class/caption * @param string $value text to check. */ public function row_column_of_table_should_contain($row, $column, $table, $value) { $tablenode = $this->get_selected_node('table', $table); $tablexpath = $tablenode->getXpath(); $rowliteral = behat_context_helper::escape($row); $valueliteral = behat_context_helper::escape($value); $columnliteral = behat_context_helper::escape($column); if (preg_match('/^-?(\d+)-?$/', $column, $columnasnumber)) { // Column indicated as a number, just use it as position of the column. $columnpositionxpath = "/child::*[position() = {$columnasnumber[1]}]"; } else { // Header can be in thead or tbody (first row), following xpath should work. $theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" . $columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]"; $tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" . $columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]"; // Check if column exists. $columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]"; $columnheader = $this->getSession()->getDriver()->find($columnheaderxpath); if (empty($columnheader)) { $columnexceptionmsg = $column . '" in table "' . $table . '"'; throw new ElementNotFoundException($this->getSession(), "\n$columnheaderxpath\n\n".'Column', null, $columnexceptionmsg); } // Following conditions were considered before finding column count. // 1. Table header can be in thead/tr/th or tbody/tr/td[1]. // 2. First column can have th (Gradebook -> user report), so having lenient sibling check. $columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath . "/preceding-sibling::*) + 1]"; } // Check if value exists in specific row/column. // Get row xpath. // GoutteDriver uses DomCrawler\Crawler and it is making XPath relative to the current context, so use descendant. $rowxpath = $tablexpath."/tbody/tr[descendant::th[normalize-space(.)=" . $rowliteral . "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]"; $columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]"; // Looks for the requested node inside the container node. $coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath); if (empty($coumnnode)) { $locatorexceptionmsg = $value . '" in "' . $row . '" row with column "' . $column; throw new ElementNotFoundException($this->getSession(), "\n$columnvaluexpath\n\n".'Column value', null, $locatorexceptionmsg); } } /** * Checks the provided value should not exist in specific row/column of table. * * @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should not contain "(?P<value_string>[^"]*)"$/ * @throws ElementNotFoundException * @param string $row row text which will be looked in. * @param string $column column text to search * @param string $table table id/class/caption * @param string $value text to check. */ public function row_column_of_table_should_not_contain($row, $column, $table, $value) { try { $this->row_column_of_table_should_contain($row, $column, $table, $value); } catch (ElementNotFoundException $e) { // Table row/column doesn't contain this value. Nothing to do. return; } // Throw exception if found. throw new ExpectationException( '"' . $column . '" with value "' . $value . '" is present in "' . $row . '" row for table "' . $table . '"', $this->getSession() ); } /** * Checks that the provided value exist in table. * * First row may contain column headers or numeric indexes of the columns * (syntax -1- is also considered to be column index). Column indexes are * useful in case of multirow headers and/or presence of cells with colspan. * * @Then /^the following should exist in the "(?P<table_string>[^"]*)" table:$/ * @throws ExpectationException * @param string $table name of table * @param TableNode $data table with first row as header and following values * | Header 1 | Header 2 | Header 3 | * | Value 1 | Value 2 | Value 3| */ public function following_should_exist_in_the_table($table, TableNode $data) { $datahash = $data->getHash(); foreach ($datahash as $row) { $firstcell = null; foreach ($row as $column => $value) { if ($firstcell === null) { $firstcell = $value; } else { $this->row_column_of_table_should_contain($firstcell, $column, $table, $value); } } } } /** * Checks that the provided values do not exist in a table. * * @Then /^the following should not exist in the "(?P<table_string>[^"]*)" table:$/ * @throws ExpectationException * @param string $table name of table * @param TableNode $data table with first row as header and following values * | Header 1 | Header 2 | Header 3 | * | Value 1 | Value 2 | Value 3| */ public function following_should_not_exist_in_the_table($table, TableNode $data) { $datahash = $data->getHash(); foreach ($datahash as $value) { $row = array_shift($value); foreach ($value as $column => $value) { try { $this->row_column_of_table_should_contain($row, $column, $table, $value); // Throw exception if found. } catch (ElementNotFoundException $e) { // Table row/column doesn't contain this value. Nothing to do. continue; } throw new ExpectationException('"' . $column . '" with value "' . $value . '" is present in "' . $row . '" row for table "' . $table . '"', $this->getSession() ); } } } /** * Given the text of a link, download the linked file and return the contents. * * This is a helper method used by {@link following_should_download_bytes()} * and {@link following_should_download_between_and_bytes()} * * @param string $link the text of the link. * @return string the content of the downloaded file. */ public function download_file_from_link($link) { // Find the link. $linknode = $this->find_link($link); $this->ensure_node_is_visible($linknode); // Get the href and check it. $url = $linknode->getAttribute('href'); if (!$url) { throw new ExpectationException('Download link does not have href attribute', $this->getSession()); } if (!preg_match('~^https?://~', $url)) { throw new ExpectationException('Download link not an absolute URL: ' . $url, $this->getSession()); } // Download the URL and check the size. $session = $this->getSession()->getCookie('MoodleSession'); return download_file_content($url, array('Cookie' => 'MoodleSession=' . $session)); } /** * Downloads the file from a link on the page and checks the size. * * Only works if the link has an href attribute. Javascript downloads are * not supported. Currently, the href must be an absolute URL. * * @Then /^following "(?P<link_string>[^"]*)" should download "(?P<expected_bytes>\d+)" bytes$/ * @throws ExpectationException * @param string $link the text of the link. * @param number $expectedsize the expected file size in bytes. */ public function following_should_download_bytes($link, $expectedsize) { $exception = new ExpectationException('Error while downloading data from ' . $link, $this->getSession()); // It will stop spinning once file is downloaded or time out. $result = $this->spin( function($context, $args) { $link = $args['link']; return $this->download_file_from_link($link); }, array('link' => $link), behat_base::get_extended_timeout(), $exception ); // Check download size. $actualsize = (int)strlen($result); if ($actualsize !== (int)$expectedsize) { throw new ExpectationException('Downloaded data was ' . $actualsize . ' bytes, expecting ' . $expectedsize, $this->getSession()); } } /** * Downloads the file from a link on the page and checks the size is in a given range. * * Only works if the link has an href attribute. Javascript downloads are * not supported. Currently, the href must be an absolute URL. * * The range includes the endpoints. That is, a 10 byte file in considered to * be between "5" and "10" bytes, and between "10" and "20" bytes. * * @Then /^following "(?P<link_string>[^"]*)" should download between "(?P<min_bytes>\d+)" and "(?P<max_bytes>\d+)" bytes$/ * @throws ExpectationException * @param string $link the text of the link. * @param number $minexpectedsize the minimum expected file size in bytes. * @param number $maxexpectedsize the maximum expected file size in bytes. */ public function following_should_download_between_and_bytes($link, $minexpectedsize, $maxexpectedsize) { // If the minimum is greater than the maximum then swap the values. if ((int)$minexpectedsize > (int)$maxexpectedsize) { list($minexpectedsize, $maxexpectedsize) = array($maxexpectedsize, $minexpectedsize); } $exception = new ExpectationException('Error while downloading data from ' . $link, $this->getSession()); // It will stop spinning once file is downloaded or time out. $result = $this->spin( function($context, $args) { $link = $args['link']; return $this->download_file_from_link($link); }, array('link' => $link), behat_base::get_extended_timeout(), $exception ); // Check download size. $actualsize = (int)strlen($result); if ($actualsize < $minexpectedsize || $actualsize > $maxexpectedsize) { throw new ExpectationException('Downloaded data was ' . $actualsize . ' bytes, expecting between ' . $minexpectedsize . ' and ' . $maxexpectedsize, $this->getSession()); } } /** * Checks that the image on the page is the same as one of the fixture files * * @Then /^the image at "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be identical to "(?P<filepath_string>(?:[^"]|\\")*)"$/ * @throws ExpectationException * @param string $element The locator of the image * @param string $selectortype The selector type * @param string $filepath path to the fixture file */ public function the_image_at_should_be_identical_to($element, $selectortype, $filepath) { global $CFG; // Get the container node (exception if it doesn't exist). $containernode = $this->get_selected_node($selectortype, $element); $url = $containernode->getAttribute('src'); if ($url == null) { throw new ExpectationException('Element does not have src attribute', $this->getSession()); } $session = $this->getSession()->getCookie('MoodleSession'); $content = download_file_content($url, array('Cookie' => 'MoodleSession=' . $session)); // Get the content of the fixture file. // Replace 'admin/' if it is in start of path with $CFG->admin . if (substr($filepath, 0, 6) === 'admin/') { $filepath = $CFG->admin . DIRECTORY_SEPARATOR . substr($filepath, 6); } $filepath = str_replace('/', DIRECTORY_SEPARATOR, $filepath); $filepath = $CFG->dirroot . DIRECTORY_SEPARATOR . $filepath; if (!is_readable($filepath)) { throw new ExpectationException('The file to compare to does not exist.', $this->getSession()); } $expectedcontent = file_get_contents($filepath); if ($content !== $expectedcontent) { throw new ExpectationException('Image is not identical to the fixture. Received ' . strlen($content) . ' bytes and expected ' . strlen($expectedcontent) . ' bytes', $this->getSession()); } } /** * Prepare to detect whether or not a new page has loaded (or the same page reloaded) some time in the future. * * @Given /^I start watching to see if a new page loads$/ */ public function i_start_watching_to_see_if_a_new_page_loads() { if (!$this->running_javascript()) { throw new DriverException('Page load detection requires JavaScript.'); } $session = $this->getSession(); if ($this->pageloaddetectionrunning || $session->getPage()->find('xpath', $this->get_page_load_xpath())) { // If we find this node at this point we are already watching for a reload and the behat steps // are out of order. We will treat this as an error - really it needs to be fixed as it indicates a problem. throw new ExpectationException( 'Page load expectation error: page reloads are already been watched for.', $session); } $this->pageloaddetectionrunning = true; $this->execute_script( 'var span = document.createElement("span"); span.setAttribute("data-rel", "' . self::PAGE_LOAD_DETECTION_STRING . '"); span.setAttribute("style", "display: none;"); document.body.appendChild(span);' ); } /** * Verify that a new page has loaded (or the same page has reloaded) since the * last "I start watching to see if a new page loads" step. * * @Given /^a new page should have loaded since I started watching$/ */ public function a_new_page_should_have_loaded_since_i_started_watching() { $session = $this->getSession(); // Make sure page load tracking was started. if (!$this->pageloaddetectionrunning) { throw new ExpectationException( 'Page load expectation error: page load tracking was not started.', $session); } // As the node is inserted by code above it is either there or not, and we do not need spin and it is safe // to use the native API here which is great as exception handling (the alternative is slow). if ($session->getPage()->find('xpath', $this->get_page_load_xpath())) { // We don't want to find this node, if we do we have an error. throw new ExpectationException( 'Page load expectation error: a new page has not been loaded when it should have been.', $session); } // Cancel the tracking of pageloaddetectionrunning. $this->pageloaddetectionrunning = false; } /** * Verify that a new page has not loaded (or the same page has reloaded) since the * last "I start watching to see if a new page loads" step. * * @Given /^a new page should not have loaded since I started watching$/ */ public function a_new_page_should_not_have_loaded_since_i_started_watching() { $session = $this->getSession(); // Make sure page load tracking was started. if (!$this->pageloaddetectionrunning) { throw new ExpectationException( 'Page load expectation error: page load tracking was not started.', $session); } // We use our API here as we can use the exception handling provided by it. $this->find( 'xpath', $this->get_page_load_xpath(), new ExpectationException( 'Page load expectation error: A new page has been loaded when it should not have been.', $this->getSession() ) ); } /** * Helper used by {@link a_new_page_should_have_loaded_since_i_started_watching} * and {@link a_new_page_should_not_have_loaded_since_i_started_watching} * @return string xpath expression. */ protected function get_page_load_xpath() { return "//span[@data-rel = '" . self::PAGE_LOAD_DETECTION_STRING . "']"; } /** * Wait unit user press Enter/Return key. Useful when debugging a scenario. * * @Then /^(?:|I )pause(?:| scenario execution)$/ */ public function i_pause_scenario_execution() { $message = "<colour:lightYellow>Paused. Press <colour:lightRed>Enter/Return<colour:lightYellow> to continue."; behat_util::pause($this->getSession(), $message); } /** * Presses a given button in the browser. * NOTE: Phantomjs and goutte driver reloads page while navigating back and forward. * * @Then /^I press the "(back|forward|reload)" button in the browser$/ * @param string $button the button to press. * @throws ExpectationException */ public function i_press_in_the_browser($button) { $session = $this->getSession(); if ($button == 'back') { $session->back(); } else if ($button == 'forward') { $session->forward(); } else if ($button == 'reload') { $session->reload(); } else { throw new ExpectationException('Unknown browser button.', $session); } } /** * Send key presses to the browser without first changing focusing, or applying the key presses to a specific * element. * * Example usage of this step: * When I type "Penguin" * * @When I type :keys * @param string $keys The key, or list of keys, to type */ public function i_type(string $keys): void { // Certain keys, such as the newline character, must be converted to the appropriate character code. // Without this, keys will behave differently depending on the browser. $keylist = array_map(function($key): string { switch ($key) { case "\n": return behat_keys::ENTER; default: return $key; } }, str_split($keys)); behat_base::type_keys($this->getSession(), $keylist); } /** * Press a named or character key with an optional set of modifiers. * * Supported named keys are: * - up * - down * - left * - right * - pageup|page_up * - pagedown|page_down * - home * - end * - insert * - delete * - backspace * - escape * - enter * - tab * * You can also use a single character for the key name e.g. 'Ctrl C'. * * Supported moderators are: * - shift * - ctrl * - alt * - meta * * Example usage of this new step: * When I press the up key * When I press the space key * When I press the shift tab key * * Multiple moderator keys can be combined using the '+' operator, for example: * When I press the ctrl+shift enter key * When I press the ctrl + shift enter key * * @When /^I press the (?P<modifiers_string>.* )?(?P<key_string>.*) key$/ * @param string $modifiers A list of keyboard modifiers, separated by the `+` character * @param string $key The name of the key to press */ public function i_press_named_key(string $modifiers, string $key): void { behat_base::require_javascript_in_session($this->getSession()); $keys = []; foreach (explode('+', $modifiers) as $modifier) { switch (strtoupper(trim($modifier))) { case '': break; case 'SHIFT': $keys[] = behat_keys::SHIFT; break; case 'CTRL': $keys[] = behat_keys::CONTROL; break; case 'ALT': $keys[] = behat_keys::ALT; break; case 'META': $keys[] = behat_keys::META; break; default: throw new \coding_exception("Unknown modifier key '$modifier'}"); } } $modifier = trim($key); switch (strtoupper($key)) { case 'UP': $keys[] = behat_keys::ARROW_UP; break; case 'DOWN': $keys[] = behat_keys::ARROW_DOWN; break; case 'LEFT': $keys[] = behat_keys::ARROW_LEFT; break; case 'RIGHT': $keys[] = behat_keys::ARROW_RIGHT; break; case 'HOME': $keys[] = behat_keys::HOME; break; case 'END': $keys[] = behat_keys::END; break; case 'INSERT': $keys[] = behat_keys::INSERT; break; case 'BACKSPACE': $keys[] = behat_keys::BACKSPACE; break; case 'DELETE': $keys[] = behat_keys::DELETE; break; case 'PAGEUP': case 'PAGE_UP': $keys[] = behat_keys::PAGE_UP; break; case 'PAGEDOWN': case 'PAGE_DOWN': $keys[] = behat_keys::PAGE_DOWN; break; case 'ESCAPE': $keys[] = behat_keys::ESCAPE; break; case 'ENTER': $keys[] = behat_keys::ENTER; break; case 'TAB': $keys[] = behat_keys::TAB; break; case 'SPACE': $keys[] = behat_keys::SPACE; break; case 'MULTIPLY': $keys[] = behat_keys::MULTIPLY; break; default: // You can enter a single ASCII character (e.g. a letter) to directly type that key. if (strlen($key) === 1) { $keys[] = strtolower($key); } else { throw new \coding_exception("Unknown key '$key'}"); } } behat_base::type_keys($this->getSession(), $keys); } /** * Trigger a keydown event for a key on a specific element. * * @When /^I press key "(?P<key_string>(?:[^"]|\\")*)" in "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ * @param string $key either char-code or character itself, * may optionally be prefixed with ctrl-, alt-, shift- or meta- * @param string $element Element we look for * @param string $selectortype The type of what we look for * @throws DriverException * @throws ExpectationException */ public function i_press_key_in_element($key, $element, $selectortype) { if (!$this->running_javascript()) { throw new DriverException('Key down step is not available with Javascript disabled'); } // Gets the node based on the requested selector type and locator. $node = $this->get_selected_node($selectortype, $element); $modifier = null; $validmodifiers = array('ctrl', 'alt', 'shift', 'meta'); $char = $key; if (strpos($key, '-')) { list($modifier, $char) = preg_split('/-/', $key, 2); $modifier = strtolower($modifier); if (!in_array($modifier, $validmodifiers)) { throw new ExpectationException(sprintf('Unknown key modifier: %s.', $modifier), $this->getSession()); } } if (is_numeric($char)) { $char = (int)$char; } $node->keyDown($char, $modifier); $node->keyPress($char, $modifier); $node->keyUp($char, $modifier); } /** * Press tab key on a specific element. * * @When /^I press tab key in "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ * @param string $element Element we look for * @param string $selectortype The type of what we look for * @throws DriverException * @throws ExpectationException */ public function i_post_tab_key_in_element($element, $selectortype) { if (!$this->running_javascript()) { throw new DriverException('Tab press step is not available with Javascript disabled'); } // Gets the node based on the requested selector type and locator. $node = $this->get_selected_node($selectortype, $element); $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); $this->execute('behat_general::i_press_named_key', ['', 'tab']); } /** * Checks if database family used is using one of the specified, else skip. (mysql, postgres, mssql, oracle, etc.) * * @Given /^database family used is one of the following:$/ * @param TableNode $databasefamilies list of database. * @return void. * @throws \Moodle\BehatExtension\Exception\SkippedException */ public function database_family_used_is_one_of_the_following(TableNode $databasefamilies) { global $DB; $dbfamily = $DB->get_dbfamily(); // Check if used db family is one of the specified ones. If yes then return. foreach ($databasefamilies->getRows() as $dbfamilytocheck) { if ($dbfamilytocheck[0] == $dbfamily) { return; } } throw new \Moodle\BehatExtension\Exception\SkippedException(); } /** * Checks if given plugin is installed, and skips the current scenario if not. * * @Given the :plugin plugin is installed * @param string $plugin frankenstyle plugin name, e.g. 'filter_embedquestion'. * @throws \Moodle\BehatExtension\Exception\SkippedException */ public function plugin_is_installed(string $plugin): void { $path = core_component::get_component_directory($plugin); if (!is_readable($path . '/version.php')) { throw new \Moodle\BehatExtension\Exception\SkippedException( 'Skipping this scenario because the ' . $plugin . ' is not installed.'); } } /** * Checks focus is with the given element. * * @Then /^the focused element is( not)? "(?P<node_string>(?:[^"]|\\")*)" "(?P<node_selector_string>[^"]*)"$/ * @param string $not optional step verifier * @param string $nodeelement Element identifier * @param string $nodeselectortype Element type * @throws DriverException If not using JavaScript * @throws ExpectationException */ public function the_focused_element_is($not, $nodeelement, $nodeselectortype) { if (!$this->running_javascript()) { throw new DriverException('Checking focus on an element requires JavaScript'); } $element = $this->find($nodeselectortype, $nodeelement); $xpath = addslashes_js($element->getXpath()); $script = 'return (function() { return document.activeElement === document.evaluate("' . $xpath . '", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; })(); '; $targetisfocused = $this->evaluate_script($script); if ($not == ' not') { if ($targetisfocused) { throw new ExpectationException("$nodeelement $nodeselectortype is focused", $this->getSession()); } } else { if (!$targetisfocused) { throw new ExpectationException("$nodeelement $nodeselectortype is not focused", $this->getSession()); } } } /** * Checks focus is with the given element. * * @Then /^the focused element is( not)? "(?P<n>(?:[^"]|\\")*)" "(?P<ns>[^"]*)" in the "(?P<c>(?:[^"]|\\")*)" "(?P<cs>[^"]*)"$/ * @param string $not string optional step verifier * @param string $element Element identifier * @param string $selectortype Element type * @param string $nodeelement Element we look in * @param string $nodeselectortype The type of selector where we look in * @throws DriverException If not using JavaScript * @throws ExpectationException */ public function the_focused_element_is_in_the($not, $element, $selectortype, $nodeelement, $nodeselectortype) { if (!$this->running_javascript()) { throw new DriverException('Checking focus on an element requires JavaScript'); } $element = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); $xpath = addslashes_js($element->getXpath()); $script = 'return (function() { return document.activeElement === document.evaluate("' . $xpath . '", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; })(); '; $targetisfocused = $this->evaluate_script($script); if ($not == ' not') { if ($targetisfocused) { throw new ExpectationException("$nodeelement $nodeselectortype is focused", $this->getSession()); } } else { if (!$targetisfocused) { throw new ExpectationException("$nodeelement $nodeselectortype is not focused", $this->getSession()); } } } /** * Manually press tab key. * * @When /^I press( shift)? tab$/ * @param string $shift string optional step verifier * @throws DriverException */ public function i_manually_press_tab($shift = '') { if (empty($shift)) { $this->execute('behat_general::i_press_named_key', ['', 'tab']); } else { $this->execute('behat_general::i_press_named_key', ['shift', 'tab']); } } /** * Trigger click on node via javascript instead of actually clicking on it via pointer. * This function resolves the issue of nested elements. * * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" skipping visibility check$/ * @param string $element * @param string $selectortype */ public function i_click_on_skipping_visibility_check($element, $selectortype) { // Gets the node based on the requested selector type and locator. $node = $this->get_selected_node($selectortype, $element); $this->js_trigger_click($node); } /** * Checks, that the specified element contains the specified text a certain amount of times. * When running Javascript tests it also considers that texts may be hidden. * * @Then /^I should see "(?P<elementscount_number>\d+)" occurrences of "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ * @throws ElementNotFoundException * @throws ExpectationException * @param int $elementscount How many occurrences of the element we look for. * @param string $text * @param string $element Element we look in. * @param string $selectortype The type of element where we are looking in. */ public function i_should_see_occurrences_of_in_element($elementscount, $text, $element, $selectortype) { // Getting the container where the text should be found. $container = $this->get_selected_node($selectortype, $element); // Looking for all the matching nodes without any other descendant matching the // same xpath (we are using contains(., ....). $xpathliteral = behat_context_helper::escape($text); $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; $nodes = $this->find_all('xpath', $xpath, false, $container); if ($this->running_javascript()) { $nodes = array_filter($nodes, function($node) { return $node->isVisible(); }); } if ($elementscount != count($nodes)) { throw new ExpectationException('Found '.count($nodes).' elements in column. Expected '.$elementscount, $this->getSession()); } } /** * Manually press enter key. * * @When /^I press enter/ * @throws DriverException */ public function i_manually_press_enter() { $this->execute('behat_general::i_press_named_key', ['', 'enter']); } /** * Visit a local URL relative to the behat root. * * @When I visit :localurl * * @param string|moodle_url $localurl The URL relative to the behat_wwwroot to visit. */ public function i_visit($localurl): void { $localurl = new moodle_url($localurl); $this->getSession()->visit($this->locate_path($localurl->out_as_local_url(false))); } /** * Increase the webdriver timeouts. * * This should be reset between scenarios, or can be called again to decrease the timeouts. * * @Given I mark this test as slow setting a timeout factor of :factor */ public function i_mark_this_test_as_long_running(int $factor = 2): void { $this->set_test_timeout_factor($factor); } /** * Click on a dynamic tab to load its content * * @Given /^I click on the "(?P<tab_string>(?:[^"]|\\")*)" dynamic tab$/ * * @param string $tabname */ public function i_click_on_the_dynamic_tab(string $tabname): void { $xpath = "//*[@id='dynamictabs-tabs'][descendant::a[contains(text(), '" . $this->escape($tabname) . "')]]"; $this->execute('behat_general::i_click_on_in_the', [$tabname, 'link', $xpath, 'xpath_element']); } /** * Enable an specific plugin. * * @When /^I enable "(?P<plugin_string>(?:[^"]|\\")*)" "(?P<plugintype_string>[^"]*)" plugin$/ * @param string $plugin Plugin we look for * @param string $plugintype The type of the plugin */ public function i_enable_plugin($plugin, $plugintype) { $class = core_plugin_manager::resolve_plugininfo_class($plugintype); $class::enable_plugin($plugin, true); } /** * Set the default text editor to the named text editor. * * @Given the default editor is set to :editor * @param string $editor * @throws ExpectationException If the specified editor is not available. */ public function the_default_editor_is_set_to(string $editor): void { global $CFG; // Check if the provided editor is available. if (!array_key_exists($editor, editors_get_available())) { throw new ExpectationException( "Unable to set the editor to {$editor} as it is not installed. The available editors are: " . implode(', ', array_keys(editors_get_available())), $this->getSession() ); } // Make the provided editor the default one in $CFG->texteditors by // moving it to the first [editor],atto,tiny,tinymce,textarea on the list. $list = explode(',', $CFG->texteditors); array_unshift($list, $editor); $list = array_unique($list); // Set the list new list of editors. set_config('texteditors', implode(',', $list)); } /** * Allow to check for minimal Moodle version. * * @Given the site is running Moodle version :minversion or higher * @param string $minversion The minimum version of Moodle required (inclusive). */ public function the_site_is_running_moodle_version_or_higher(string $minversion): void { global $CFG; require_once($CFG->libdir . '/environmentlib.php'); $currentversion = normalize_version(get_config('', 'release')); if (version_compare($currentversion, $minversion, '<')) { throw new Moodle\BehatExtension\Exception\SkippedException( 'Site must be running Moodle version ' . $minversion . ' or higher' ); } } /** * Allow to check for maximum Moodle version. * * @Given the site is running Moodle version :maxversion or lower * @param string $maxversion The maximum version of Moodle required (inclusive). */ public function the_site_is_running_moodle_version_or_lower(string $maxversion): void { global $CFG; require_once($CFG->libdir . '/environmentlib.php'); $currentversion = normalize_version(get_config('', 'release')); if (version_compare($currentversion, $maxversion, '>')) { throw new Moodle\BehatExtension\Exception\SkippedException( 'Site must be running Moodle version ' . $maxversion . ' or lower' ); } } } behat_navigation.php 0000644 00000172336 15151222201 0010562 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Navigation steps definitions. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Mink\Exception\ExpectationException as ExpectationException; use Behat\Mink\Exception\DriverException as DriverException; use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; /** * Steps definitions to navigate through the navigation tree nodes. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_navigation extends behat_base { /** * Checks whether a navigation node is active within the block navigation. * * @Given i should see :name is active in navigation * * @throws ElementNotFoundException * @param string $element The name of the nav elemnent to look for. * @return void */ public function i_should_see_is_active_in_navigation($element) { $this->execute("behat_general::assert_element_contains_text", [$element, '.block_navigation .active_tree_node', 'css_element']); } /** * Helper function to get a navigation nodes text element given its text from within the navigation block. * * This function finds the node with the given text from within the navigation block. * It checks to make sure the node is visible, and then returns it. * * @param string $text * @param bool $branch Set this true if you're only interested in the node if its a branch. * @param null|bool $collapsed Set this to true or false if you want the node to either be collapsed or not. * If its left as null then we don't worry about it. * @param null|string|Exception|false $exception The exception to throw if the node is not found. * @return \Behat\Mink\Element\NodeElement */ protected function get_node_text_node($text, $branch = false, $collapsed = null, $exception = null) { if ($exception === null) { $exception = new ExpectationException('The "' . $text . '" node could not be found', $this->getSession()); } else if (is_string($exception)) { $exception = new ExpectationException($exception, $this->getSession()); } $nodetextliteral = behat_context_helper::escape($text); $hasblocktree = "[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]"; $hasbranch = "[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]"; $hascollapsed = "li[@aria-expanded='false']/p"; $notcollapsed = "li[@aria-expanded='true']/p"; $match = "[normalize-space(.)={$nodetextliteral}]"; // Avoid problems with quotes. $isbranch = ($branch) ? $hasbranch : ''; if ($collapsed === true) { $iscollapsed = $hascollapsed; } else if ($collapsed === false) { $iscollapsed = $notcollapsed; } else { $iscollapsed = 'li/p'; } // First check root nodes, it can be a span or link. $xpath = "//ul{$hasblocktree}/{$hascollapsed}{$isbranch}/span{$match}|"; $xpath .= "//ul{$hasblocktree}/{$hascollapsed}{$isbranch}/a{$match}|"; // Next search for the node containing the text within a link. $xpath .= "//ul{$hasblocktree}//ul/{$iscollapsed}{$isbranch}/a{$match}|"; // Finally search for the node containing the text within a span. $xpath .= "//ul{$hasblocktree}//ul/{$iscollapsed}{$isbranch}/span{$match}"; $node = $this->find('xpath', $xpath, $exception); $this->ensure_node_is_visible($node); return $node; } /** * Returns true if the navigation node with the given text is expandable. * * @Given /^navigation node "([^"]*)" should be expandable$/ * * @throws ExpectationException * @param string $nodetext * @return bool */ public function navigation_node_should_be_expandable($nodetext) { if (!$this->running_javascript()) { // Nodes are only expandable when JavaScript is enabled. return false; } $node = $this->get_node_text_node($nodetext, true); $node = $node->getParent(); if ($node->hasClass('emptybranch')) { throw new ExpectationException('The "' . $nodetext . '" node is not expandable', $this->getSession()); } return true; } /** * Returns true if the navigation node with the given text is not expandable. * * @Given /^navigation node "([^"]*)" should not be expandable$/ * * @throws ExpectationException * @param string $nodetext * @return bool */ public function navigation_node_should_not_be_expandable($nodetext) { if (!$this->running_javascript()) { // Nodes are only expandable when JavaScript is enabled. return false; } $node = $this->get_node_text_node($nodetext); $node = $node->getParent(); if ($node->hasClass('emptybranch') || $node->hasClass('tree_item')) { return true; } throw new ExpectationException('The "' . $nodetext . '" node is expandable', $this->getSession()); } /** * Click on an entry in the user menu. * @Given /^I follow "(?P<nodetext_string>(?:[^"]|\\")*)" in the user menu$/ * * @param string $nodetext */ public function i_follow_in_the_user_menu($nodetext) { if ($this->running_javascript()) { // The user menu must be expanded when JS is enabled. $xpath = "//div[contains(concat(' ', @class, ' '), ' usermenu ')]//a[contains(concat(' ', @class, ' '), ' dropdown-toggle ')]"; $this->execute("behat_general::i_click_on", array($this->escape($xpath), "xpath_element")); } // Now select the link. // The CSS path is always present, with or without JS. $csspath = ".usermenu .dropdown-menu"; $this->execute('behat_general::i_click_on_in_the', array($nodetext, "link", $csspath, "css_element") ); } /** * Expands the selected node of the navigation tree that matches the text. * @Given /^I expand "(?P<nodetext_string>(?:[^"]|\\")*)" node$/ * * @throws ExpectationException * @param string $nodetext * @return bool|void */ public function i_expand_node($nodetext) { // This step is useless with Javascript disabled as Moodle auto expands // all of tree's nodes; adding this because of scenarios that shares the // same steps with and without Javascript enabled. if (!$this->running_javascript()) { if ($nodetext === get_string('administrationsite')) { // Administration menu is not loaded by default any more. Click the link to expand. $this->execute('behat_general::i_click_on_in_the', array($nodetext, "link", get_string('administration'), "block") ); return true; } return true; } $node = $this->get_node_text_node($nodetext, true, true, 'The "' . $nodetext . '" node can not be expanded'); // Check if the node is a link AND a branch. if (strtolower($node->getTagName()) === 'a') { // We just want to expand the node, we don't want to follow it. $node = $node->getParent(); } $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } /** * Collapses the selected node of the navigation tree that matches the text. * * @Given /^I collapse "(?P<nodetext_string>(?:[^"]|\\")*)" node$/ * @throws ExpectationException * @param string $nodetext * @return bool|void */ public function i_collapse_node($nodetext) { // No collapsible nodes with non-JS browsers. if (!$this->running_javascript()) { return true; } $node = $this->get_node_text_node($nodetext, true, false, 'The "' . $nodetext . '" node can not be collapsed'); // Check if the node is a link AND a branch. if (strtolower($node->getTagName()) === 'a') { // We just want to expand the node, we don't want to follow it. $node = $node->getParent(); } $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } /** * Finds a node in the Navigation or Administration tree * * @param string $nodetext * @param array $parentnodes * @param string $nodetype node type (link or text) * @return NodeElement|null * @throws ExpectationException when one of the parent nodes is not found */ protected function find_node_in_navigation($nodetext, $parentnodes, $nodetype = 'link') { // Site admin is different and needs special treatment. $siteadminstr = get_string('administrationsite'); // Create array of all parentnodes. $countparentnode = count($parentnodes); // If JS is disabled and Site administration is not expanded we // should follow it, so all the lower-level nodes are available. if (!$this->running_javascript()) { if ($parentnodes[0] === $siteadminstr) { // We don't know if there if Site admin is already expanded so // don't wait, it is non-JS and we already waited for the DOM. $siteadminlink = $this->getSession()->getPage()->find('named_exact', array('link', "'" . $siteadminstr . "'")); if ($siteadminlink) { $this->execute('behat_general::i_click_on', [$siteadminlink, 'NodeElement']); } } } // Get top level node. $node = $this->get_top_navigation_node($parentnodes[0]); // Expand all nodes. for ($i = 0; $i < $countparentnode; $i++) { if ($i > 0) { // Sub nodes within top level node. $node = $this->get_navigation_node($parentnodes[$i], $node); } // The p node contains the aria jazz. $pnodexpath = "/p[contains(concat(' ', normalize-space(@class), ' '), ' tree_item ')]"; $pnode = $node->find('xpath', $pnodexpath); $linode = $pnode->getParent(); // Keep expanding all sub-parents if js enabled. if ($pnode && $this->running_javascript() && $linode->hasAttribute('aria-expanded') && ($linode->getAttribute('aria-expanded') == "false")) { $this->js_trigger_click($pnode); // Wait for node to load, if not loaded before. if ($linode->hasAttribute('data-loaded') && $linode->getAttribute('data-loaded') == "false") { $jscondition = '(document.evaluate("' . $linode->getXpath() . '", document, null, '. 'XPathResult.ANY_TYPE, null).iterateNext().getAttribute(\'data-loaded\') == "true")'; $this->getSession()->wait(behat_base::get_extended_timeout() * 1000, $jscondition); } } } // Finally, click on requested node under navigation. $nodetextliteral = behat_context_helper::escape($nodetext); $tagname = ($nodetype === 'link') ? 'a' : 'span'; $xpath = "/ul/li/p[contains(concat(' ', normalize-space(@class), ' '), ' tree_item ')]" . "/{$tagname}[normalize-space(.)=" . $nodetextliteral . "]"; return $node->find('xpath', $xpath); } /** * Finds a node in the Navigation or Administration tree and clicks on it. * * @param string $nodetext * @param array $parentnodes * @throws ExpectationException */ protected function select_node_in_navigation($nodetext, $parentnodes) { $nodetoclick = $this->find_node_in_navigation($nodetext, $parentnodes); // Throw exception if no node found. if (!$nodetoclick) { throw new ExpectationException('Navigation node "' . $nodetext . '" not found under "' . implode(' > ', $parentnodes) . '"', $this->getSession()); } $this->execute('behat_general::i_click_on', [$nodetoclick, 'NodeElement']); } /** * Helper function to get top navigation node in tree. * * @throws ExpectationException if note not found. * @param string $nodetext name of top navigation node in tree. * @return NodeElement */ protected function get_top_navigation_node($nodetext) { // Avoid problems with quotes. $nodetextliteral = behat_context_helper::escape($nodetext); $exception = new ExpectationException('Top navigation node "' . $nodetext . ' not found in "', $this->getSession()); // First find in navigation block. $xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]" . "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . "/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/*[contains(normalize-space(.), " . $nodetextliteral .")]]" . "|" . "//div[contains(concat(' ', normalize-space(@class), ' '), ' card-text ')]/div" . "/ul[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]" . "/li[p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/*[contains(normalize-space(.), " . $nodetextliteral .")]]"; $node = $this->find('xpath', $xpath, $exception); return $node; } /** * Helper function to get sub-navigation node. * * @throws ExpectationException if note not found. * @param string $nodetext node to find. * @param NodeElement $parentnode parent navigation node. * @return NodeElement. */ protected function get_navigation_node($nodetext, $parentnode = null) { // Avoid problems with quotes. $nodetextliteral = behat_context_helper::escape($nodetext); $xpath = "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[child::p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/child::span[normalize-space(.)=" . $nodetextliteral ."]]"; $node = $parentnode->find('xpath', $xpath); if (!$node) { $xpath = "/ul/li[contains(concat(' ', normalize-space(@class), ' '), ' contains_branch ')]" . "[child::p[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]" . "/child::a[normalize-space(.)=" . $nodetextliteral ."]]"; $node = $parentnode->find('xpath', $xpath); } if (!$node) { throw new ExpectationException('Sub-navigation node "' . $nodetext . '" not found under "' . $parentnode->getText() . '"', $this->getSession()); } return $node; } /** * Step to open the navigation bar if it is needed. * * The top log in and log out links are hidden when middle or small * size windows (or devices) are used. This step returns a step definition * clicking to expand the navbar if it is hidden. * * @Given /^I expand navigation bar$/ */ public function get_expand_navbar_step() { // Checking if we need to click the navbar button to show the navigation menu, it // is hidden by default when using clean theme and a medium or small screen size. // The DOM and the JS should be all ready and loaded. Running without spinning // as this is a widely used step and we can not spend time here trying to see // a DOM node that is not always there (at the moment clean is not even the // default theme...). $navbuttonjs = "return ( Y.one('.btn-navbar') && Y.one('.btn-navbar').getComputedStyle('display') !== 'none' )"; // Adding an extra click we need to show the 'Log in' link. if (!$this->evaluate_script($navbuttonjs)) { return false; } $this->execute('behat_general::i_click_on', array(".btn-navbar", "css_element")); } /** * Go to current page setting item * * This can be used on front page, course, category or modules pages. * * @Given /^I navigate to "(?P<nodetext_string>(?:[^"]|\\")*)" in current page administration$/ * * @throws ExpectationException * @param string $nodetext navigation node to click, may contain path, for example "Reports > Overview" * @return void */ public function i_navigate_to_in_current_page_administration($nodetext) { $nodelist = array_map('trim', explode('>', $nodetext)); $this->select_from_administration_menu($nodelist); } /** * Checks that current page administration contains text * * @Given /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in current page administration$/ * * @throws ExpectationException * @param string $element The locator of the specified selector. * This may be a path, for example "Subscription mode > Forced subscription" * @param string $selectortype The selector type (link or text) * @return void */ public function should_exist_in_current_page_administration($element, $selectortype) { $nodes = array_map('trim', explode('>', $element)); $nodetext = end($nodes); // Find administration menu. if (!$menuxpath = $this->find_page_action_menu()) { $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(true); } $this->toggle_page_administration_menu($menuxpath); $this->execute('behat_general::should_exist_in_the', [$nodetext, $selectortype, $menuxpath, 'xpath_element']); $this->toggle_page_administration_menu($menuxpath); } /** * Checks that current page administration contains text * * @Given /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in current page administration$/ * * @throws ExpectationException * @param string $element The locator of the specified selector. * This may be a path, for example "Subscription mode > Forced subscription" * @param string $selectortype The selector type (link or text) * @return void */ public function should_not_exist_in_current_page_administration($element, $selectortype) { $nodes = array_map('trim', explode('>', $element)); $nodetext = end($nodes); // Find administration menu. $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(); if (!$menuxpath) { // Menu not found, exit. return; } $this->toggle_page_administration_menu($menuxpath); $this->execute('behat_general::should_not_exist_in_the', [$nodetext, $selectortype, $menuxpath, 'xpath_element']); $this->toggle_page_administration_menu($menuxpath); } /** * Go to site administration item * * @Given /^I navigate to "(?P<nodetext_string>(?:[^"]|\\")*)" in site administration$/ * * @throws ExpectationException * @param string $nodetext navigation node to click, may contain path, for example "Reports > Overview" * @return void */ public function i_navigate_to_in_site_administration($nodetext) { $nodelist = array_map('trim', explode('>', $nodetext)); $this->i_select_from_primary_navigation(get_string('administrationsite')); $this->select_on_administration_page($nodelist); } /** * Opens the current users profile page in edit mode. * * @Given /^I open my profile in edit mode$/ * @throws coding_exception * @return void */ public function i_open_my_profile_in_edit_mode() { global $USER; $user = $this->get_session_user(); $globuser = $USER; $USER = $user; // We need this set to the behat session user so we can call isloggedin. $systemcontext = context_system::instance(); $bodynode = $this->find('xpath', 'body'); $bodyclass = $bodynode->getAttribute('class'); $matches = []; if (preg_match('/(?<=^course-|\scourse-)\d+/', $bodyclass, $matches) && !empty($matches)) { $courseid = intval($matches[0]); } else { $courseid = SITEID; } if (isloggedin() && !isguestuser($user) && !is_mnet_remote_user($user)) { if (is_siteadmin($user) || has_capability('moodle/user:update', $systemcontext)) { $url = new moodle_url('/user/editadvanced.php', array('id' => $user->id, 'course' => SITEID, 'returnto' => 'profile')); } else if (has_capability('moodle/user:editownprofile', $systemcontext)) { $userauthplugin = false; if (!empty($user->auth)) { $userauthplugin = get_auth_plugin($user->auth); } if ($userauthplugin && $userauthplugin->can_edit_profile()) { $url = $userauthplugin->edit_profile_url(); if (empty($url)) { if (empty($course)) { $url = new moodle_url('/user/edit.php', array('id' => $user->id, 'returnto' => 'profile')); } else { $url = new moodle_url('/user/edit.php', array('id' => $user->id, 'course' => $courseid, 'returnto' => 'profile')); } } } } $this->execute('behat_general::i_visit', [$url]); } // Restore global user variable. $USER = $globuser; } /** * Open a given page, belonging to a plugin or core component. * * The page-type are interpreted by each plugin to work out the * corresponding URL. See the resolve_url method in each class like * behat_mod_forum. That method should document which page types are * recognised, and how the name identifies them. * * For pages belonging to core, the 'core > ' bit is omitted. * * @When /^I am on the (?<page>[^ "]*) page$/ * @When /^I am on the "(?<page>[^"]*)" page$/ * * @param string $page the component and page name. * E.g. 'Admin notifications' or 'core_user > Preferences'. * @throws Exception if the specified page cannot be determined. */ public function i_am_on_page(string $page) { $this->execute('behat_general::i_visit', [$this->resolve_page_helper($page)]); } /** * Open a given page logged in as a given user. * * This is like the combination * When I log in as "..." * And I am on the "..." page * but with the advantage that you go straight to the desired page, without * having to wait for the Dashboard to load. * * @When /^I am on the (?<page>[^ "]*) page logged in as (?<username>[^ "]*)$/ * @When /^I am on the "(?<page>[^"]*)" page logged in as (?<username>[^ "]*)$/ * @When /^I am on the (?<page>[^ "]*) page logged in as "(?<username>[^ "]*)"$/ * @When /^I am on the "(?<page>[^"]*)" page logged in as "(?<username>[^ "]*)"$/ * * @param string $page the type of page. E.g. 'Admin notifications' or 'core_user > Preferences'. * @param string $username the name of the user to log in as. E.g. 'admin'. * @throws Exception if the specified page cannot be determined. */ public function i_am_on_page_logged_in_as(string $page, string $username) { self::execute('behat_auth::i_log_in_as', [$username, $this->resolve_page_helper($page)]); } /** * Helper used by i_am_on_page() and i_am_on_page_logged_in_as(). * * @param string $page the type of page. E.g. 'Admin notifications' or 'core_user > Preferences'. * @return moodle_url the corresponding URL. */ protected function resolve_page_helper(string $page): moodle_url { list($component, $name) = $this->parse_page_name($page); if ($component === 'core') { return $this->resolve_core_page_url($name); } else { $context = behat_context_helper::get('behat_' . $component); return $context->resolve_page_url($name); } } /** * Parse a full page name like 'Admin notifications' or 'core_user > Preferences'. * * E.g. parsing 'mod_quiz > View' gives ['mod_quiz', 'View']. * * @param string $page the full page name * @return array with two elements, component and page name. */ protected function parse_page_name(string $page): array { $dividercount = substr_count($page, ' > '); if ($dividercount === 0) { return ['core', $page]; } else if ($dividercount === 1) { list($component, $name) = explode(' > ', $page); if ($component === 'core') { throw new coding_exception('Do not specify the component "core > ..." for core pages.'); } return [$component, $name]; } else { throw new coding_exception('The page name must be in the form ' . '"{page-name}" for core pages, or "{component} > {page-name}" ' . 'for pages belonging to other components. ' . 'For example "Admin notifications" or "mod_quiz > View".'); } } /** * Open a given instance of a page, belonging to a plugin or core component. * * The instance identifier and page-type are interpreted by each plugin to * work out the corresponding URL. See the resolve_page_instance_url method * in each class like behat_mod_forum. That method should document which page * types are recognised, and how the name identifies them. * * For pages belonging to core, the 'core > ' bit is omitted. * * @When /^I am on the (?<identifier>[^ "]*) (?<type>[^ "]*) page$/ * @When /^I am on the "(?<identifier>[^"]*)" "(?<type>[^"]*)" page$/ * @When /^I am on the (?<identifier>[^ "]*) "(?<type>[^"]*)" page$/ * @When /^I am on the "(?<identifier>[^"]*)" (?<type>[^ "]*) page$/ * * @param string $identifier identifies the particular page. E.g. 'Test quiz'. * @param string $type the component and page type. E.g. 'mod_quiz > View'. * @throws Exception if the specified page cannot be determined. */ public function i_am_on_page_instance(string $identifier, string $type) { $this->execute('behat_general::i_visit', [$this->resolve_page_instance_helper($identifier, $type)]); } /** * Open a given page logged in as a given user. * * This is like the combination * When I log in as "..." * And I am on the "..." "..." page * but with the advantage that you go straight to the desired page, without * having to wait for the Dashboard to load. * * @When /^I am on the (?<identifier>[^ "]*) (?<type>[^ "]*) page logged in as (?<username>[^ "]*)$/ * @When /^I am on the "(?<identifier>[^"]*)" "(?<type>[^"]*)" page logged in as (?<username>[^ "]*)$/ * @When /^I am on the (?<identifier>[^ "]*) "(?<type>[^"]*)" page logged in as (?<username>[^ "]*)$/ * @When /^I am on the "(?<identifier>[^"]*)" (?<type>[^ "]*) page logged in as (?<username>[^ "]*)$/ * @When /^I am on the (?<identifier>[^ "]*) (?<type>[^ "]*) page logged in as "(?<username>[^"]*)"$/ * @When /^I am on the "(?<identifier>[^"]*)" "(?<type>[^"]*)" page logged in as "(?<username>[^"]*)"$/ * @When /^I am on the (?<identifier>[^ "]*) "(?<type>[^"]*)" page logged in as "(?<username>[^"]*)"$/ * @When /^I am on the "(?<identifier>[^"]*)" (?<type>[^ "]*) page logged in as "(?<username>[^"]*)"$/ * * @param string $identifier identifies the particular page. E.g. 'Test quiz'. * @param string $type the component and page type. E.g. 'mod_quiz > View'. * @param string $username the name of the user to log in as. E.g. 'student'. * @throws Exception if the specified page cannot be determined. */ public function i_am_on_page_instance_logged_in_as(string $identifier, string $type, string $username) { self::execute('behat_auth::i_log_in_as', [$username, $this->resolve_page_instance_helper($identifier, $type)]); } /** * Helper used by i_am_on_page() and i_am_on_page_logged_in_as(). * * @param string $identifier identifies the particular page. E.g. 'Test quiz'. * @param string $pagetype the component and page type. E.g. 'mod_quiz > View'. * @return moodle_url the corresponding URL. */ protected function resolve_page_instance_helper(string $identifier, string $pagetype): moodle_url { list($component, $type) = $this->parse_page_name($pagetype); if ($component === 'core') { return $this->resolve_core_page_instance_url($type, $identifier); } else { $context = behat_context_helper::get('behat_' . $component); return $context->resolve_page_instance_url($type, $identifier); } } /** * Convert core page names to URLs for steps like 'When I am on the "[page name]" page'. * * Recognised page names are: * | Homepage | Homepage (normally dashboard). | * | Admin notifications | Admin notification screen. | * * @param string $name identifies which identifies this page, e.g. 'Homepage', 'Admin notifications'. * @return moodle_url the corresponding URL. * @throws Exception with a meaningful error message if the specified page cannot be found. */ protected function resolve_core_page_url(string $name): moodle_url { switch ($name) { case 'Homepage': return new moodle_url('/'); case 'My courses': return new moodle_url('/my/courses.php'); case 'Admin notifications': return new moodle_url('/admin/'); default: throw new Exception('Unrecognised core page type "' . $name . '."'); } } /** * Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'. * * Recognised page names are: * | Page type | Identifier meaning | description | * | Category | category idnumber | List of courses in that category. | * | Course | course shortname | Main course home pag | * | Course editing | course shortname | Edit settings page for the course | * | Activity | activity idnumber | Start page for that activity | * | Activity editing | activity idnumber | Edit settings page for that activity | * | [modname] Activity | activity name or idnumber | Start page for that activity | * | [modname] Activity editing | activity name or idnumber | Edit settings page for that activity | * | Backup | course shortname | Course to backup | * | Import | course shortname | Course import from | * | Restore | course shortname | Course to restore from | * | Reset | course shortname | Course to reset | * | Course copy | course shortname | Course to copy | * | Groups | course shortname | Groups page for the course | * | Permissions | course shortname | Permissions page for the course | * | Enrolment methods | course shortname | Enrolment methods for the course | * | Enrolled users | course shortname | The main participants page | * | Other users | course shortname | The course other users page | * * Examples: * * When I am on the "Welcome to ECON101" "forum activity" page logged in as student1 * * @param string $type identifies which type of page this is, e.g. 'Category page'. * @param string $identifier identifies the particular page, e.g. 'test-cat'. * @return moodle_url the corresponding URL. * @throws Exception with a meaningful error message if the specified page cannot be found. */ protected function resolve_core_page_instance_url(string $type, string $identifier): moodle_url { $type = strtolower($type); switch ($type) { case 'category': $categoryid = $this->get_category_id($identifier); if (!$categoryid) { throw new Exception('The specified category with idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/course/index.php', ['categoryid' => $categoryid]); case 'course editing': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/course/edit.php', ['id' => $courseid]); case 'course': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/course/view.php', ['id' => $courseid]); case 'activity': $cm = $this->get_course_module_for_identifier($identifier); if (!$cm) { throw new Exception('The specified activity with idnumber "' . $identifier . '" does not exist'); } return $cm->url; case 'activity editing': $cm = $this->get_course_module_for_identifier($identifier); if (!$cm) { throw new Exception('The specified activity with idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/course/modedit.php', [ 'update' => $cm->id, ]); case 'backup': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/backup/backup.php', ['id' => $courseid]); case 'import': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/backup/import.php', ['id' => $courseid]); case 'restore': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } $context = context_course::instance($courseid); return new moodle_url('/backup/restorefile.php', ['contextid' => $context->id]); case 'reset': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/course/reset.php', ['id' => $courseid]); case 'course copy': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/backup/copy.php', ['id' => $courseid]); case 'groups': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/group/index.php', ['id' => $courseid]); case 'permissions': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } $context = context_course::instance($courseid); return new moodle_url('/admin/roles/permissions.php', ['contextid' => $context->id]); case 'enrolment methods': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/enrol/instances.php', ['id' => $courseid]); case 'enrolled users': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/user/index.php', ['id' => $courseid]); case 'other users': $courseid = $this->get_course_id($identifier); if (!$courseid) { throw new Exception('The specified course with shortname, fullname, or idnumber "' . $identifier . '" does not exist'); } return new moodle_url('/enrol/otherusers.php', ['id' => $courseid]); } $parts = explode(' ', $type); if (count($parts) > 1) { if ($parts[1] === 'activity') { $modname = $parts[0]; $cm = $this->get_cm_by_activity_name($modname, $identifier); if (count($parts) == 2) { // View page. return new moodle_url($cm->url); } if ($parts[2] === 'editing') { // Edit settings page. return new moodle_url('/course/modedit.php', ['update' => $cm->id]); } if ($parts[2] === 'roles') { // Locally assigned roles page. return new moodle_url('/admin/roles/assign.php', ['contextid' => $cm->context->id]); } if ($parts[2] === 'permissions') { // Permissions page. return new moodle_url('/admin/roles/permissions.php', ['contextid' => $cm->context->id]); } } } throw new Exception('Unrecognised core page type "' . $type . '."'); } /** * Opens the course homepage. (Consider using 'I am on the "shortname" "Course" page' step instead.) * * @Given /^I am on "(?P<coursefullname_string>(?:[^"]|\\")*)" course homepage$/ * @throws coding_exception * @param string $coursefullname The full name of the course. * @return void */ public function i_am_on_course_homepage($coursefullname) { $courseid = $this->get_course_id($coursefullname); $url = new moodle_url('/course/view.php', ['id' => $courseid]); $this->execute('behat_general::i_visit', [$url]); } /** * Open the course homepage with editing mode enabled. * * @param string $coursefullname The course full name of the course. */ public function i_am_on_course_homepage_with_editing_mode_on($coursefullname) { $this->i_am_on_course_homepage_with_editing_mode_set_to($coursefullname, 'on'); } /** * Open the course homepage with editing mode set to either on, or off. * * @Given I am on :coursefullname course homepage with editing mode :onoroff * @throws coding_exception * @param string $coursefullname The course full name of the course. * @param string $onoroff Whehter to switch editing on, or off. */ public function i_am_on_course_homepage_with_editing_mode_set_to(string $coursefullname, string $onoroff): void { $courseid = $this->get_course_id($coursefullname); $url = new moodle_url('/course/view.php', ['id' => $courseid]); // Visit the course page. $this->execute('behat_general::i_visit', [$url]); switch ($onoroff) { case 'on': $this->execute('behat_navigation::i_turn_editing_mode_on'); break; case 'off': $this->execute('behat_navigation::i_turn_editing_mode_off'); break; default: throw new \coding_exception("Unknown editing mode '{$onoroff}'. Accepted values are 'on' and 'off'"); } } /** * Opens the flat navigation drawer if it is not already open * * @When /^I open flat navigation drawer$/ * @throws ElementNotFoundException Thrown by behat_base::find */ public function i_open_flat_navigation_drawer() { if (!$this->running_javascript()) { // Navigation drawer is always open without JS. return; } $xpath = "//button[contains(@data-action,'toggle-drawer')]"; $node = $this->find('xpath', $xpath); $expanded = $node->getAttribute('aria-expanded'); if ($expanded === 'false') { $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); $this->ensure_node_attribute_is_set($node, 'aria-expanded', 'true'); } } /** * Closes the flat navigation drawer if it is open (does nothing if JS disabled) * * @When /^I close flat navigation drawer$/ * @throws ElementNotFoundException Thrown by behat_base::find */ public function i_close_flat_navigation_drawer() { if (!$this->running_javascript()) { // Navigation drawer can not be closed without JS. return; } $xpath = "//button[contains(@data-action,'toggle-drawer')]"; $node = $this->find('xpath', $xpath); $expanded = $node->getAttribute('aria-expanded'); if ($expanded === 'true') { $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } } /** * Clicks link with specified id|title|alt|text in the primary navigation * * @When /^I select "(?P<link_string>(?:[^"]|\\")*)" from primary navigation$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $link */ public function i_select_from_primary_navigation(string $link) { $this->execute('behat_general::i_click_on_in_the', [$link, 'link', '.primary-navigation .moremenu.navigation', 'css_element'] ); } /** * Clicks link with specified id|title|alt|text in the secondary navigation * * @When I select :link from secondary navigation * @throws ElementNotFoundException Thrown by behat_base::find * @param string $link */ public function i_select_from_secondary_navigation(string $link) { $this->execute('behat_general::i_click_on_in_the', [$link, 'link', '.secondary-navigation .moremenu.navigation', 'css_element'] ); } /** * If we are not on the course main page, click on the course link in the navbar */ protected function go_to_main_course_page() { $url = $this->getSession()->getCurrentUrl(); if (!preg_match('|/course/view.php\?id=[\d]+$|', $url)) { $node = $this->find('xpath', '//header//div[@id=\'page-navbar\']//a[contains(@href,\'/course/view.php?id=\')]' ); $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } } /** * Finds and clicks a link on the admin page (site administration or course administration) * * @param array $nodelist */ protected function select_on_administration_page($nodelist) { $parentnodes = $nodelist; $lastnode = array_pop($parentnodes); $xpath = '//section[@id=\'region-main\']'; // Check if there is a separate tab for this submenu of the page. If found go to it. if ($parentnodes) { $tabname = behat_context_helper::escape($parentnodes[0]); $tabxpath = '//ul[@role=\'tablist\']/li/a[contains(normalize-space(.), ' . $tabname . ')]'; $menubarxpath = '//ul[@role=\'menubar\']/li/a[contains(normalize-space(.), ' . $tabname . ')]'; $linkname = behat_context_helper::escape(get_string('moremenu')); $menubarmorexpath = '//ul[contains(@class,\'more-nav\')]/li/a[contains(normalize-space(.), ' . $linkname . ')]'; $tabnode = $this->getSession()->getPage()->find('xpath', $tabxpath); $menunode = $this->getSession()->getPage()->find('xpath', $menubarxpath); $menubuttons = $this->getSession()->getPage()->findAll('xpath', $menubarmorexpath); if ($tabnode || $menunode) { $node = is_object($tabnode) ? $tabnode : $menunode; if ($this->running_javascript()) { $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); // Click on the tab and add 'active' tab to the xpath. $xpath .= '//div[contains(@class,\'active\')]'; } else { // Add the tab content selector to the xpath. $tabid = behat_context_helper::escape(ltrim($node->getAttribute('href'), '#')); $xpath .= '//div[@id = ' . $tabid . ']'; } array_shift($parentnodes); } else if (count($menubuttons) > 0) { try { $menubuttons[0]->isVisible(); try { $this->execute('behat_general::i_click_on', [$menubuttons[1], 'NodeElement']); } catch (Exception $e) { $this->execute('behat_general::i_click_on', [$menubuttons[0], 'NodeElement']); } $moreitemxpath = '//ul[@data-region=\'moredropdown\']/li/a[contains(normalize-space(.), ' . $tabname . ')]'; if ($morenode = $this->getSession()->getPage()->find('xpath', $moreitemxpath)) { $this->execute('behat_general::i_click_on', [$morenode, 'NodeElement']); $xpath .= '//div[contains(@class,\'active\')]'; array_shift($parentnodes); } } catch (Exception $e) { } } } // Find a section with the parent name in it. if ($parentnodes) { // Find the section on the page (links may be repeating in different sections). $section = behat_context_helper::escape($parentnodes[0]); $xpath .= '//div[@class=\'row\' and contains(.,'.$section.')]'; } // Find a link and click on it. $linkname = behat_context_helper::escape($lastnode); $xpathlink = $xpathbutton = $xpath; $xpathlink .= '//a[contains(normalize-space(.), ' . $linkname . ')]'; $xpathbutton .= '//button[contains(normalize-space(.), ' . $linkname . ')]'; if ($node = $this->getSession()->getPage()->find('xpath', $xpathbutton)) { $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } else if (!$node = $this->getSession()->getPage()->find('xpath', $xpathlink)) { throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '"'); } else { $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } } /** * Locates the administration menu in the <header> element and returns its xpath * * @param bool $mustexist if specified throws an exception if menu is not found * @return null|string */ protected function find_header_administration_menu($mustexist = false) { $menuxpath = '//div[contains(@class,\'secondary-navigation\')]//nav[contains(@class,\'moremenu\')]'; if ($mustexist) { $exception = new ElementNotFoundException($this->getSession(), 'Page header administration menu'); $this->find('xpath', $menuxpath, $exception); } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) { return null; } return $menuxpath; } /** * Locates the administration menu on the page (but not in the header) and returns its xpath * * @param bool $mustexist if specified throws an exception if menu is not found * @return null|string */ protected function find_page_administration_menu($mustexist = false) { $menuxpath = '//div[@id=\'region-main-settings-menu\']'; if ($mustexist) { $exception = new ElementNotFoundException($this->getSession(), 'Page administration menu'); $this->find('xpath', $menuxpath, $exception); } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) { return null; } return $menuxpath; } /** * Locates the action menu on the page (but not in the header) and returns its xpath * * @param null|bool $mustexist if specified throws an exception if menu is not found * @return null|string */ protected function find_page_action_menu($mustexist = false) { $menuxpath = '//div[@id=\'action-menu-0-menubar\']'; if ($mustexist) { $exception = new ElementNotFoundException($this->getSession(), 'Page check'); $this->find('xpath', $menuxpath, $exception); } else if (!$this->getSession()->getPage()->find('xpath', $menuxpath)) { return null; } return $menuxpath; } /** * Toggles administration menu * * @param string $menuxpath (optional) xpath to the page administration menu if already known */ protected function toggle_page_administration_menu($menuxpath = null) { if (!$menuxpath) { $menuxpath = $this->find_header_administration_menu() ?: $this->find_page_administration_menu(); } if ($menuxpath && $this->running_javascript()) { $node = $this->find('xpath', $menuxpath . '//a[@data-toggle=\'dropdown\']'); if ($node->isVisible()) { $this->execute('behat_general::i_click_on', [$node, 'NodeElement']); } } } /** * Finds a page edit cog and select an item from it * * If the page edit cog is in the page header and the item is not found there, click "More..." link * and find the item on the course/frontpage administration page * * @param array $nodelist * @throws ElementNotFoundException */ protected function select_from_administration_menu($nodelist) { // Find administration menu. if ($menuxpath = $this->find_header_administration_menu()) { $isheader = true; } else if ($menuxpath = $this->find_page_action_menu(true)) { $isheader = false; } else { $menuxpath = $this->find_page_administration_menu(true); $isheader = false; } $this->execute('behat_navigation::toggle_page_administration_menu', [$menuxpath]); $firstnode = $nodelist[0]; $firstlinkname = behat_context_helper::escape($firstnode); $firstlink = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $firstlinkname . ')]' ); if (!$isheader || count($nodelist) == 1) { $lastnode = end($nodelist); $linkname = behat_context_helper::escape($lastnode); $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]'); if ($link) { $this->execute('behat_general::i_click_on', [$link, 'NodeElement']); return; } } else if ($firstlink) { $this->execute('behat_general::i_click_on', [$firstlink, 'NodeElement']); array_splice($nodelist, 0, 1); $this->select_on_administration_page($nodelist); return; } if ($isheader) { // Front page administration will have subnodes under "More...". $linkname = behat_context_helper::escape(get_string('morenavigationlinks')); $link = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $linkname . ')]' ); // Course administration will have subnodes under "Course administration". $courselinkname = behat_context_helper::escape(get_string('courseadministration')); $courselink = $this->getSession()->getPage()->find('xpath', $menuxpath . '//a[contains(normalize-space(.), ' . $courselinkname . ')]' ); if ($link) { $this->execute('behat_general::i_click_on', [$link, 'NodeElement']); $this->select_on_administration_page($nodelist); return; } else if ($courselink) { $this->execute('behat_general::i_click_on', [$courselink, 'NodeElement']); $this->select_on_administration_page($nodelist); return; } } throw new ElementNotFoundException($this->getSession(), 'Link "' . join(' > ', $nodelist) . '" in the current page edit menu"'); } /** * Visit a fixture page for testing stuff that is not available in core. * * Please always, to prevent unwanted requests, protect behat fixture files with: * defined('BEHAT_SITE_RUNNING') || die(); * * @Given /^I am on fixture page "(?P<url_string>(?:[^"]|\\")*)"$/ * @param string $url local path to fixture page */ public function i_am_on_fixture_page($url) { $fixtureregex = '|^/[a-z0-9_\-/]*/tests/behat/fixtures/[a-z0-9_\-]*\.php$|'; if (!preg_match($fixtureregex, $url)) { throw new coding_exception("URL {$url} is not a fixture URL"); } $this->execute('behat_general::i_visit', [$url]); } /** * First checks to see if we are on this page via the breadcrumb. If not we then attempt to follow the link name given. * * @param string $pagename Name of the breadcrumb item to check and follow. * @Given /^I follow the breadcrumb "(?P<url_string>(?:[^"]|\\")*)"$/ */ public function go_to_breadcrumb_location(string $pagename): void { $link = $this->getSession()->getPage()->find( 'xpath', "//nav[@aria-label='Navigation bar']/ol/li[last()][contains(normalize-space(.), '" . $pagename . "')]" ); if (!$link) { $this->execute("behat_general::i_click_on_in_the", [$pagename, 'link', 'page', 'region']); } } /** * Checks whether an item exists in the user menu. * * @Given :itemtext :selectortype should exist in the user menu * @Given :itemtext :selectortype should :not exist in the user menu * * @throws ElementNotFoundException * @param string $itemtext The menu item to find * @param string $selectortype The selector type * @param string|null $not Instructs to checks whether the element does not exist in the user menu, if defined * @return void */ public function should_exist_in_user_menu($itemtext, $selectortype, $not = null) { $callfunction = is_null($not) ? 'should_exist_in_the' : 'should_not_exist_in_the'; $this->execute("behat_general::{$callfunction}", [$itemtext, $selectortype, $this->get_user_menu_xpath(), 'xpath_element']); } /** * Checks whether an item exists in a given user submenu. * * @Given :itemtext :selectortype should exist in the :submenuname user submenu * @Given :itemtext :selectortype should :not exist in the :submenuname user submenu * * @throws ElementNotFoundException * @param string $itemtext The submenu item to find * @param string $selectortype The selector type * @param string $submenuname The name of the submenu * @param string|null $not Instructs to checks whether the element does not exist in the user menu, if defined * @return void */ public function should_exist_in_user_submenu($itemtext, $selectortype, $submenuname, $not = null) { $callfunction = is_null($not) ? 'should_exist_in_the' : 'should_not_exist_in_the'; $this->execute("behat_general::{$callfunction}", [$itemtext, $selectortype, $this->get_user_submenu_xpath($submenuname), 'xpath_element']); } /** * Checks whether a given user submenu is visible. * * @Then /^I should see "(?P<submenu_string>[^"]*)" user submenu$/ * * @throws ElementNotFoundException * @throws ExpectationException * @param string $submenuname The name of the submenu * @return void */ public function i_should_see_user_submenu($submenuname) { $this->execute('behat_general::should_be_visible', array($this->get_user_submenu_xpath($submenuname), 'xpath_element')); } /** * Return the xpath for the user menu element. * * @return string The xpath */ protected function get_user_menu_xpath() { return "//div[contains(concat(' ', @class, ' '), ' usermenu ')]" . "//div[contains(concat(' ', @class, ' '), ' dropdown-menu ')]" . "//div[@id='carousel-item-main']"; } /** * Return the xpath for a given user submenu element. * * @param string $submenuname The name of the submenu * @return string The xpath */ protected function get_user_submenu_xpath($submenuname) { return "//div[contains(concat(' ', @class, ' '), ' usermenu ')]" . "//div[contains(concat(' ', @class, ' '), ' dropdown-menu ')]" . "//div[contains(concat(' ', @class, ' '), ' submenu ')][@aria-label='" . $submenuname . "']"; } /** * Returns whether the user can edit the current page. * * @return bool */ protected function is_editing_on() { $body = $this->find('xpath', "//body", false, false, 0); return $body->hasClass('editing'); } /** * Turns editing mode on. * @Given I switch editing mode on * @Given I turn editing mode on */ public function i_turn_editing_mode_on() { $this->execute('behat_forms::i_set_the_field_to', [get_string('editmode'), 1]); if (!$this->running_javascript()) { $this->execute('behat_general::i_click_on', [ get_string('setmode', 'core'), 'button', ]); } if (!$this->is_editing_on()) { throw new ExpectationException('The edit mode could not be turned on', $this->getSession()); } } /** * Turns editing mode off. * @Given I switch editing mode off * @Given I turn editing mode off */ public function i_turn_editing_mode_off() { $this->execute('behat_forms::i_set_the_field_to', [get_string('editmode'), 0]); if (!$this->running_javascript()) { $this->execute('behat_general::i_click_on', [ get_string('setmode', 'core'), 'button', ]); } if ($this->is_editing_on()) { throw new ExpectationException('The edit mode could not be turned off', $this->getSession()); } } } timezone.feature 0000644 00000000657 15151222201 0007752 0 ustar 00 @core Feature: View timezone defaults In order to run all other behat tests As an admin I need to verify the default timezone is Australia/Perth Scenario: Admin sees default timezone Australia/Perth When I log in as "admin" And I navigate to "Location > Location settings" in site administration Then I should see "Default: Australia/Perth" And the field "Default timezone" matches value "Australia/Perth" userfeedback.feature 0000644 00000004271 15151222201 0010537 0 ustar 00 @core Feature: Gathering user feedback In order to facilitate data collection from as broad a sample of Moodle users as possible As Moodle HQ We should add a link within Moodle to a permanent URL on which surveys will be placed Scenario: Users should see a feedback link on footer when the feature is enabled Given the following config values are set as admin: | enableuserfeedback | 1 | When I log in as "admin" Then I should see "Give feedback" in the "page-footer" "region" Scenario: Users should not see a feedback link on footer when the feature is disabled Given the following config values are set as admin: | enableuserfeedback | 0 | When I log in as "admin" Then I should not see "Give feedback" in the "page-footer" "region" Scenario: Visitors should not see a feedback link on footer when they are not logged in Given the following config values are set as admin: | enableuserfeedback | 1 | When I am on site homepage Then I should not see "Give feedback" in the "page-footer" "region" @javascript Scenario: Users should not see the notification after they click on the remind me later link Given the following config values are set as admin: | enableuserfeedback | 1 | | userfeedback_nextreminder | 2 | | userfeedback_remindafter | 90 | When I log in as "admin" And I follow "Dashboard" And I click on "Remind me later" "link" And I reload the page Then I should not see "Give feedback" in the "region-main" "region" And I should not see "Remind me later" in the "region-main" "region" @javascript Scenario: Users should not see the notification after they click on the give feedback link Given the following config values are set as admin: | enableuserfeedback | 1 | | userfeedback_nextreminder | 2 | | userfeedback_remindafter | 90 | When I log in as "admin" And I follow "Dashboard" And I click on "Give feedback" "link" And I close all opened windows And I reload the page Then I should not see "Give feedback" in the "region-main" "region" And I should not see "Remind me later" in the "region-main" "region" datetime_any.feature 0000644 00000010371 15151222201 0010555 0 ustar 00 @core @javascript @core_form Feature: Any day / month / year combination in date form elements works ok. In order to use date / datetime elements with Behat as a user Any day / month / year combination must work ok @javascript Scenario Outline: Verify that setting any date / datetime is possible with enabled fields Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activity" exist: | activity | name | intro | course | idnumber | | assign | Assignment 01 | Assign activity to test some dates | C1 | assign01 | And I am on the "Assignment 01" "assign activity editing" page logged in as admin And I expand all fieldsets And I set the field "Due date" to "<initial_date>" And I set the field "Due date" to "<final_date>" When I press "Save and display" Then the activity date in "Assignment 01" should contain "Due:" And the activity date in "Assignment 01" should contain "<date_result>" Examples: | initial_date | final_date | date_result | case_explanation (times Australia/Perth) | | ##today## | ##tomorrow noon## | ##tomorrow noon##%A, %d %B %Y, %I:%M## | change of day, any day, back and forth | | ##tomorrow## | ##today noon## | ##today noon##%A, %d %B %Y, %I:%M## | | | 1617256800 | 1617170400 | Wednesday, 31 March 2021, 2:00 | change of month, back and forth | | 1617170400 | 1617256800 | Thursday, 1 April 2021, 2:00 | | | 1740808800 | 1709186400 | Thursday, 29 February 2024, 2:00 | change of month, leap year, back and forth | | 1709186400 | 1740808800 | Saturday, 1 March 2025, 2:00 | | | 1577858400 | 1577772000 | Tuesday, 31 December 2019, 2:00 | change of year, back and forth | | 1577772000 | 1577858400 | Wednesday, 1 January 2020, 2:00 | | @javascript Scenario Outline: Verify that setting any date / datetime is possible with disabled fields Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activity" exist: | activity | name | intro | course | idnumber | | assign | Assignment 01 | Assign activity to test some dates | C1 | assign01 | And I am on the "Assignment 01" "assign activity editing" page logged in as admin And I expand all fieldsets And I set the field "Due date" to "<initial_date>" And I set the field "Due date" to "disabled" And I set the field "Due date" to "<final_date>" When I press "Save and display" Then the activity date in "Assignment 01" should contain "Due:" And the activity date in "Assignment 01" should contain "<date_result>" Examples: | initial_date | final_date | date_result | case_explanation (times Australia/Perth) | | ##today## | ##tomorrow noon## | ##tomorrow noon##%A, %d %B %Y, %I:%M## | change of day, any day, back and forth | | ##tomorrow## | ##today noon## | ##today noon##%A, %d %B %Y, %I:%M## | | | 1617256800 | 1617170400 | Wednesday, 31 March 2021, 2:00 | change of month, back and forth | | 1617170400 | 1617256800 | Thursday, 1 April 2021, 2:00 | | | 1740808800 | 1709186400 | Thursday, 29 February 2024, 2:00 | change of month, leap year, back and forth | | 1709186400 | 1740808800 | Saturday, 1 March 2025, 2:00 | | | 1577858400 | 1577772000 | Tuesday, 31 December 2019, 2:00 | change of year, back and forth | | 1577772000 | 1577858400 | Wednesday, 1 January 2020, 2:00 | | alpha_chooser.feature 0000644 00000052463 15151222201 0010731 0 ustar 00 @core Feature: Initials bar In order to filter users from user list As an admin I need to be able to use letter filters Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher | Ateacher | Teacher | teacher@example.com | | student1 | Astudent | Astudent | student1@example.com | | student2 | Bstudent | Astudent | student2@example.com | | student3 | Cstudent | Cstudent | student3@example.com | | student4 | Cstudent | Cstudent | student4@example.com | | student5 | Cstudent | Cstudent | student5@example.com | | student6 | Cstudent | Cstudent | student6@example.com | | student7 | Cstudent | Cstudent | student7@example.com | | student8 | Cstudent | Cstudent | student8@example.com | | student9 | Cstudent | Cstudent | student9@example.com | | student10 | Cstudent | Cstudent | student10@example.com | | student11 | Cstudent | Cstudent | student11@example.com | | student12 | Cstudent | Cstudent | student12@example.com | | student13 | Cstudent | Cstudent | student13@example.com | | student14 | Cstudent | Cstudent | student14@example.com | | student15 | Cstudent | Cstudent | student15@example.com | | student16 | Cstudent | Cstudent | student16@example.com | | student17 | Cstudent | Cstudent | student17@example.com | | student18 | Cstudent | Cstudent | student18@example.com | | student19 | Cstudent | Cstudent | student19@example.com | | student20 | Cstudent | Cstudent | student20@example.com | | student21 | Cstudent | Cstudent | student21@example.com | | student22 | Cstudent | Cstudent | student22@example.com | | student23 | Cstudent | Cstudent | student23@example.com | | student24 | Cstudent | Cstudent | student24@example.com | And the following "courses" exist: | fullname | shortname | category |enablecompletion | | Course 1 | C1 | 0 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | | student4 | C1 | student | | student5 | C1 | student | | student6 | C1 | student | | student7 | C1 | student | | student8 | C1 | student | | student9 | C1 | student | | student10 | C1 | student | | student11 | C1 | student | | student12 | C1 | student | | student13 | C1 | student | | student14 | C1 | student | | student15 | C1 | student | | student16 | C1 | student | | student17 | C1 | student | | student18 | C1 | student | | student19 | C1 | student | | student20 | C1 | student | | student21 | C1 | student | | student22 | C1 | student | | student23 | C1 | student | | student24 | C1 | student | Scenario: Filter users on assignment submission page Given the following "activities" exist: | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | | assign | C1 | assign1 | TestAssignment | Test assignment description | 0 | 0 | And I am on the "assign1" "Activity" page logged in as "teacher" When I follow "View all submissions" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" And I click on "A" "link" in the ".initialbar.lastinitial .page-item.A" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "B" "link" in the ".initialbar.firstinitial .page-item.B" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I am on the "assign1" "Activity" page When I follow "View all submissions" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.lastinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" Scenario: Filter users on view gradebook page Given the following "activities" exist: | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | | assign | C1 | assign1 | TestAssignment | Test assignment description | 0 | 0 | And I am on the "assign1" "Activity" page logged in as "teacher" When I follow "View all submissions" And I select "View gradebook" from the "jump" singleselect And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" And I click on "A" "link" in the ".initialbar.lastinitial .page-item.A" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "B" "link" in the ".initialbar.firstinitial .page-item.B" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I am on the "assign1" "Activity" page When I follow "View all submissions" And I select "View gradebook" from the "jump" singleselect And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.lastinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" Scenario: Filter users on course participants page Given the following "activities" exist: | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | | assign | C1 | assign1 | TestAssignment | Test assignment description | 0 | 0 | And I am on the "C1" "Course" page logged in as "student1" And I log out And I am on the "C1" "Course" page logged in as "student2" And I log out And I am on the "C1" "Course" page logged in as "teacher" And I follow "Participants" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" And I click on "A" "link" in the ".initialbar.lastinitial .page-item.A" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "B" "link" in the ".initialbar.firstinitial .page-item.B" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I am on "Course 1" course homepage And I follow "Participants" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.lastinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" @javascript Scenario: Filter users on activity completion page Given the following "activities" exist: | activity | course | idnumber | name | intro | assignsubmission_onlinetext_enabled | assignsubmission_file_enabled | | assign | C1 | assign1 | TestAssignment | Test assignment description | 0 | 0 | And I am on the "assign1" "assign Activity editing" page logged in as "admin" And I expand all fieldsets And I set the field "Completion tracking" to "1" And I click on "Save and return to course" "button" And I navigate to "Course completion" in current page administration And I expand all fieldsets And I click on "Assignment - TestAssignment" "checkbox" And I click on "Save changes" "button" And I log out And I am on the "C1" "Course" page logged in as "teacher" And I navigate to "Reports" in current page administration And I click on "Activity completion" "link" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" And I click on "A" "link" in the ".initialbar.lastinitial .page-item.A" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "B" "link" in the ".initialbar.firstinitial .page-item.B" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I am on "Course 1" course homepage And I navigate to "Reports" in current page administration And I click on "Activity completion" "link" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should not see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should not see "Cstudent Cstudent" And I click on "All" "link" in the ".initialbar.lastinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.firstinitial" "css_element" And ".initialbarall.page-item.active" "css_element" should exist in the ".initialbar.lastinitial" "css_element" And ".page-item.active.B" "css_element" should not exist in the ".initialbar.firstinitial" "css_element" And ".page-item.active.A" "css_element" should not exist in the ".initialbar.lastinitial" "css_element" And I should see "Astudent Astudent" And I should see "Bstudent Astudent" And I should see "Cstudent Cstudent" behat_transformations.php 0000644 00000013074 15151222201 0011645 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Behat arguments transformations. * * This methods are used by Behat CLI command. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Gherkin\Node\TableNode; /** * Transformations to apply to steps arguments. * * This methods are applied to the steps arguments that matches * the regular expressions specified in the @Transform tag. * * @package core * @category test * @copyright 2013 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_transformations extends behat_base { /** * @deprecated since Moodle 3.2 */ public function prefixed_tablenode_transformations() { throw new coding_exception('prefixed_tablenode_transformations() can not be used anymore. ' . 'Please use tablenode_transformations() instead.'); } /** * Removes escaped argument delimiters. * * We use double quotes as arguments delimiters and * to add the " as part of an argument we escape it * with a backslash, this method removes this backslash. * * @Transform /^((.*)"(.*))$/ * @param string $string * @return string The string with the arguments fixed. */ public function arg_replace_slashes($string) { if (!is_scalar($string)) { return $string; } return str_replace('\"', '"', $string); } /** * Replaces $NASTYSTRING vars for a nasty string. * * @Transform /^((.*)\$NASTYSTRING(\d)(.*))$/ * @param string $argument The whole argument value. * @return string */ public function arg_replace_nasty_strings($argument) { if (!is_scalar($argument)) { return $argument; } return $this->replace_nasty_strings($argument); } /** * Convert string time to timestamp. * Use ::time::STRING_TIME_TO_CONVERT::DATE_FORMAT:: * * @Transform /^##(.*)##$/ * @param string $time * @return int timestamp. */ public function arg_time_to_string($time) { return $this->get_transformed_timestamp($time); } /** * Transformations for TableNode arguments. * * Transformations applicable to TableNode arguments should also * be applied, adding them in a different method for Behat API restrictions. * * @Transform table:* * @param TableNode $tablenode * @return TableNode The transformed table */ public function tablenode_transformations(TableNode $tablenode) { // Walk through all values including the optional headers. $rows = $tablenode->getRows(); foreach ($rows as $rowkey => $row) { foreach ($row as $colkey => $value) { // Transforms vars into nasty strings. if (preg_match('/\$NASTYSTRING(\d)/', $rows[$rowkey][$colkey])) { $rows[$rowkey][$colkey] = $this->replace_nasty_strings($rows[$rowkey][$colkey]); } // Transform time. if (preg_match('/^##(.*)##$/', $rows[$rowkey][$colkey], $match)) { if (isset($match[1])) { $rows[$rowkey][$colkey] = $this->get_transformed_timestamp($match[1]); } } } } // Return the transformed TableNode. unset($tablenode); $tablenode = new TableNode($rows); return $tablenode; } /** * Replaces $NASTYSTRING vars for a nasty string. * * Method reused by TableNode tranformation. * * @param string $string * @return string */ public function replace_nasty_strings($string) { return preg_replace_callback( '/\$NASTYSTRING(\d)/', function ($matches) { return nasty_strings::get($matches[0]); }, $string ); } /** * Return timestamp for the time passed. * * @param string $time time to convert * @return string */ protected function get_transformed_timestamp($time) { $timepassed = explode('##', $time); // If not a valid time string, then just return what was passed. if ((($timestamp = strtotime($timepassed[0])) === false)) { return $time; } $count = count($timepassed); if ($count === 2) { // If timestamp with specified strftime format, then return formatted date string. return userdate($timestamp, $timepassed[1]); } else if ($count === 1) { return $timestamp; } else { // If not a valid time string, then just return what was passed. return $time; } } } securelayout.feature 0000644 00000003271 15151222201 0010637 0 ustar 00 @core Feature: Page displaying with secure layout In order to securely perform tasks As a student I need not to be able to exit the page using the header logo Background: # Get to the fixture page. Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | | label | L1 | <a href="../lib/tests/fixtures/securetestpage.php">Fixture link</a> | C1 | label1 | Scenario: Confirm that there is no header link Given I am on the "C1" "Course" page logged in as "admin" When I follow "Fixture link" Then I should see "Acceptance test site" in the "nav" "css_element" But "Acceptance test site" "link" should not exist Scenario: Confirm that the user name is displayed in the navbar without a link Given I log in as "admin" And the following config values are set as admin: | logininfoinsecurelayout | 1 | And I am on "Course 1" course homepage When I follow "Fixture link" Then I should see "You are logged in as Admin User" in the "nav" "css_element" But "Logout" "link" should not exist Scenario: Confirm that the custom menu items do not appear when language selection is enabled Given I log in as "admin" And the following config values are set as admin: | langmenuinsecurelayout | 1 | | custommenuitems | -This is a custom item\|/customurl/ | And I am on "Course 1" course homepage When I follow "Fixture link" Then I should not see "This is a custom item" in the "nav" "css_element" action_modal.feature 0000644 00000004073 15151222201 0010545 0 ustar 00 @core Feature: Close modals by clicking outside them In order to easily close the currently open pop-up As a user Clicking outside the modal should close it if it doesn't contain a form. @javascript Scenario: The popup closes when clicked on dead space - YUI Given the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "activities" exist: | activity | name | intro | course | idnumber | | quiz | Test quiz name | Test quiz description | C1 | quiz1 | And I am on the "quiz1" "Activity" page logged in as "admin" And I follow "Add question" And I click on "Add" "link" And I click on "a new question" "link" # Cannot use the normal ‘I click on’ here, because the pop-up gets in the way. And I click on ".moodle-dialogue-lightbox" "css_element" skipping visibility check # The modal does not close because it contains a form. Then I should see "Choose a question type to add" @javascript Scenario: The popup closes when clicked on dead space - Modal Given I log in as "admin" And I follow "Full calendar" And I press "New event" When I click on "[data-region='modal-container']" "css_element" # The modal does not close becaue it contains a form. Then ".modal-backdrop" "css_element" should be visible # Confirm that the contents of the new calendar event modal are visible. And I should see "New event" in the ".modal-title" "css_element" And I should see "Event title" in the ".modal-body" "css_element" @javascript Scenario: The popup help closes when clicked Given the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "activities" exist: | activity | name | intro | course | idnumber | | quiz | Test quiz name | Test quiz description | C1 | quiz1 | And I am on the "quiz1" "Activity" page logged in as "admin" And I follow "Add question" Then I should not see "More help" showuseridentity.feature 0000644 00000010415 15151222201 0011542 0 ustar 00 @core Feature: Select user identity fields In order to see who users are at my institution As an administrator I can configure which user fields show with lists of users Background: Given the following "custom profile fields" exist: | datatype | shortname | name | param2 | | text | speciality | Speciality | 255 | | checkbox | fool | Foolish | | | text | thesis | Thesis | 100000 | And the following "users" exist: | username | department | profile_field_speciality | email | | user1 | Amphibians | Frogs | email1@example.org | | user2 | Undead | Zombies | email2@example.org | And the following "courses" exist: | shortname | fullname | | C1 | Course 1 | And the following "course enrolments" exist: | user | course | role | | user1 | C1 | manager | | user2 | C1 | manager | Scenario: The admin settings screen should show text custom fields of certain length (and let you choose them) When I log in as "admin" And I navigate to "Users > Permissions > User policies" in site administration Then I should see "Speciality" in the "#admin-showuseridentity" "css_element" And I should not see "Foolish" in the "#admin-showuseridentity" "css_element" And I should not see "Thesis" in the "#admin-showuseridentity" "css_element" And I set the field "Speciality" to "1" And I press "Save changes" And the field "Speciality" matches value "1" Scenario: The admin settings screen correctly formats custom field names Given the "multilang" filter is "on" And the "multilang" filter applies to "content and headings" And the following "custom profile field" exists: | datatype | text | | name | <span class="multilang" lang="en">Field (EN)</span><span class="multilang" lang="de">Field (DE)</span> | | shortname | stuff | | param2 | 100 | When I log in as "admin" And I navigate to "Users > Permissions > User policies" in site administration Then I should see "Field (EN)" in the "#admin-showuseridentity" "css_element" And I should not see "Field (DE)" in the "#admin-showuseridentity" "css_element" Scenario: When you choose custom fields, these should be displayed in the 'Browse list of users' screen Given the following config values are set as admin: | showuseridentity | username,department,profile_field_speciality | When I log in as "admin" And I navigate to "Users > Accounts > Browse list of users" in site administration Then I should see "Speciality" in the "thead" "css_element" And I should see "Department" in the "thead" "css_element" And I should not see "Email" in the "thead" "css_element" Then I should see "Amphibians" in the "user1" "table_row" And I should see "Frogs" in the "user1" "table_row" And I should not see "email1@example.org" And I should see "Undead" in the "user2" "table_row" And I should see "Zombies" in the "user2" "table_row" And I should not see "email2@example.org" Scenario: When you choose custom fields, these should be displayed in the 'Participants' screen Given the following config values are set as admin: | showuseridentity | username,department,profile_field_speciality | When I am on the "C1" "Course" page logged in as "user1" And I navigate to course participants Then I should see "Frogs" in the "user1" "table_row" And I should see "Zombies" in the "user2" "table_row" @javascript Scenario: The user filtering options on the participants screen should work for custom profile fields Given the following config values are set as admin: | showuseridentity | username,department,profile_field_speciality | When I am on the "C1" "Course" page logged in as "admin" And I navigate to course participants And I set the field "type" in the "Filter 1" "fieldset" to "Keyword" And I set the field "Type..." in the "Filter 1" "fieldset" to "Frogs" # You have to tab out to make it actually apply. And I press tab And I click on "Apply filters" "button" Then I should see "user1" in the "participants" "table" And I should not see "user2" in the "participants" "table" behat_filters.php 0000644 00000005711 15151222201 0010063 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Steps definitions related to filters. * * @package core * @category test * @copyright 2018 the Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // Note: You cannot use MOODLE_INTERNAL test here, or include files which do so. // This file is required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); /** * Steps definitions related to filters. * * @package core * @category test * @copyright 2018 the Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_filters extends behat_base { /** * Set the global filter configuration. * * @Given /^the "(?P<filter_name>(?:[^"]|\\")*)" filter is "(on|off|disabled)"$/ * * @param string $filtername the name of a filter, e.g. 'glossary'. * @param string $statename 'on', 'off' or 'disabled'. */ public function the_filter_is($filtername, $statename) { require_once(__DIR__ . '/../../filterlib.php'); switch ($statename) { case 'on': $state = TEXTFILTER_ON; break; case 'off': $state = TEXTFILTER_OFF; break; case 'disabled': $state = TEXTFILTER_DISABLED; break; default: throw new coding_exception('Unknown filter state: ' . $statename); } filter_set_global_state($filtername, $state); } /** * Set the global filter target. * * @Given /^the "(?P<filter_name>(?:[^"]|\\")*)" filter applies to "(content|content and headings)"$/ * * @param string $filtername the name of a filter, e.g. 'glossary'. * @param string $filtertarget 'content' or 'content and headings'. */ public function the_filter_applies_to($filtername, $filtertarget) { switch ($filtertarget) { case 'content and headings': filter_set_applies_to_strings($filtername, 1); break; case 'content': filter_set_applies_to_strings($filtername, 0); break; default: throw new coding_exception('Unknown filter target: ' . $filtertarget); } } } switch_editing_mode.feature 0000644 00000005541 15151222201 0012125 0 ustar 00 @core @turn_edit_mode_on @javascript Feature: Turn editing mode on Users should be able to turn editing mode on and off Background: Given the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I turn editing mode off And I log out Scenario: Edit mode on page Gradebook Given the following "activities" exist: | activity | course | idnumber | name | intro | | assign | C1 | assign1 | Test Assignment 1 | Test Assignment 1 | And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "View > Grader report" in the course gradebook And I turn editing mode on And "Edit assign Test Assignment 1" "link" should exist And I turn editing mode off Then "Edit assign Test Assignment 1" "link" should not exist Scenario: Edit mode on page Course And I log in as "teacher1" And I am on "Course 1" course homepage And I turn editing mode on And I should see "Add an activity or resource" And I turn editing mode off Then I should not see "Add an activity or resource" Scenario: Edit mode on page Homepage Given I log in as "admin" And I am on site homepage And I turn editing mode on And I should see "Add an activity or resource" And I turn editing mode off Then I should not see "Add an activity or resource" Scenario: Edit mode on page Default profile Given I log in as "admin" And I navigate to "Appearance > Default profile page" in site administration And I turn editing mode on And I should see "Add a block" And I turn editing mode off Then I should not see "Add a block" Scenario: Edit mode on page Profile Given I log in as "admin" And I follow "View profile" And I turn editing mode on And I should see "Add a block" And I turn editing mode off Then I should not see "Add a block" Scenario: Edit mode on page Default dashboard Given I log in as "admin" And I navigate to "Appearance > Default Dashboard page" in site administration And I turn editing mode on And I should see "Add a block" And I turn editing mode off Then I should not see "Add a block" Scenario: Edit mode on page Dashboard And I log in as "teacher1" And I turn editing mode on And I should see "Add a block" Then I turn editing mode off Then I should not see "Add a block" expand_single_fieldset.feature 0000644 00000001271 15151222201 0012610 0 ustar 00 @core Feature: Expand single fieldset in Behat tests In order to expand all fieldsets when there is only one As a developer I need Behat to successfully expand that fieldset @javascript Scenario: Test expand all fieldsets when there is only one fieldset Given I log in as "admin" # This page was selected because it only has one fieldset. When I navigate to "Users > Accounts > Upload users" in site administration # Close the fieldset manually... And I click on "//a[@data-toggle='collapse']" "xpath_element" And I should not see "Example text file" # Expand using 'expand all' step. And I expand all fieldsets Then I should see "Example text file" behat_accessibility.php 0000644 00000017341 15151222201 0011244 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Steps definitions to open and close action menus. * * @package core * @category test * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use Behat\Mink\Exception\{DriverException, ExpectationException}; // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); /** * Steps definitions to assist with accessibility testing. * * @package core * @category test * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_accessibility extends behat_base { /** * Run the axe-core accessibility tests. * * There are standard tags to ensure WCAG 2.1 A, WCAG 2.1 AA, and Section 508 compliance. * It is also possible to specify any desired optional tags. * * The list of available tags can be found at * https://github.com/dequelabs/axe-core/blob/v3.5.5/doc/rule-descriptions.md. * * @Then the page should meet accessibility standards * @Then the page should meet accessibility standards with :extratags extra tests * @Then the page should meet :standardtags accessibility standards * @param string $standardtags Comma-separated list of standard tags to run * @param string $extratags Comma-separated list of tags to run in addition to the standard tags */ public function run_axe_validation_for_tags(string $standardtags = '', string $extratags = ''): void { $this->run_axe_for_tags( // Turn the comma-separated string into an array of trimmed values, filtering out empty values. array_filter(array_map('trim', explode(',', $standardtags))), array_filter(array_map('trim', explode(',', $extratags))) ); } /** * Run the Axe tests. * * See https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md for details of the supported * tags. * * @param array $standardtags The list of standard tags to run * @param array $extratags The list of tags, in addition to the standard tags, to run */ protected function run_axe_for_tags(array $standardtags = [], array $extratags = []): void { if (!behat_config_manager::get_behat_run_config_value('axe')) { return; } if (!$this->has_tag('accessibility')) { throw new DriverException( 'Accessibility tests using Axe must have the @accessibility tag on either the scenario or feature.' ); } $this->require_javascript(); $axeurl = (new \moodle_url('/lib/behat/axe/axe.min.js'))->out(false); $axeconfig = $this->get_axe_config_for_tags($standardtags, $extratags); $runaxe = <<<EOF (axeurl => { const runTests = () => { const axeTag = document.querySelector('script[data-purpose="axe"]'); axeTag.dataset.results = null; axe.run({$axeconfig}) .then(results => { axeTag.dataset.results = JSON.stringify({ violations: results.violations, exception: null, }); }) .catch(exception => { axeTag.dataset.results = JSON.stringify({ violations: [], exception: exception, }); }); }; if (document.querySelector('script[data-purpose="axe"]')) { runTests(); } else { // Inject the axe content. const axeTag = document.createElement('script'); axeTag.src = axeurl, axeTag.dataset.purpose = 'axe'; axeTag.onload = () => runTests(); document.head.append(axeTag); } })('{$axeurl}'); EOF; $this->execute_script($runaxe); $getresults = <<<EOF return (() => { const axeTag = document.querySelector('script[data-purpose="axe"]'); return axeTag.dataset.results; })() EOF; for ($i = 0; $i < self::get_extended_timeout() * 10; $i++) { $results = json_decode($this->evaluate_script($getresults) ?? ''); if ($results) { break; } } if (empty($results)) { throw new \Exception('No data'); } if ($results->exception !== null) { throw new ExpectationException($results->exception, $this->session); } $violations = $results->violations; if (!count($violations)) { return; } $violationdata = "Accessibility violations found:\n"; foreach ($violations as $violation) { $nodedata = ''; foreach ($violation->nodes as $node) { $failedchecks = []; foreach (array_merge($node->any, $node->all, $node->none) as $check) { $failedchecks[$check->id] = $check->message; } $nodedata .= sprintf( " - %s:\n %s\n\n", implode(', ', $failedchecks), implode("\n ", $node->target) ); } $violationdata .= sprintf( " %.03d violations of '%s' (severity: %s)\n%s\n", count($violation->nodes), $violation->description, $violation->impact, $nodedata ); } throw new ExpectationException($violationdata, $this->getSession()); } /** * Get the configuration to use with Axe. * * See https://github.com/dequelabs/axe-core/blob/develop/doc/rule-descriptions.md for details of the rules. * * @param array|null $standardtags The list of standard tags to run * @param array|null $extratags The list of tags, in addition to the standard tags, to run * @return string The JSON-encoded configuration. */ protected function get_axe_config_for_tags(?array $standardtags = null, ?array $extratags = null): string { if (empty($standardtags)) { $standardtags = [ // Meet WCAG 2.1 A requirements. 'wcag2a', // Meet WCAG 2.1 AA requirements. 'wcag2aa', // Meet Section 508 requirements. // See https://www.epa.gov/accessibility/what-section-508 for detail. 'section508', // Ensure that ARIA attributes are correctly defined. 'cat.aria', // Requiremetns for sensory and visual cues. // These largely related to viewport scale and zoom functionality. 'cat.sensory-and-visual-cues', // Meet WCAG 1.3.4 requirements for orientation. // See https://www.w3.org/WAI/WCAG21/Understanding/orientation.html for detail. 'wcag134', ]; } return json_encode([ 'runOnly' => [ 'type' > 'tag', 'values' => array_merge($standardtags, $extratags), ], ]); } } behat_data_generators.php 0000644 00000023316 15151222201 0011556 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Data generators for acceptance testing. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Gherkin\Node\TableNode as TableNode; use Behat\Behat\Tester\Exception\PendingException as PendingException; /** * Class to set up quickly a Given environment. * * The entry point is the Behat steps: * the following "entity types" exist: * | test | data | * * Entity type will either look like "users" or "activities" for core entities, or * "mod_forum > subscription" or "core_message > message" for entities belonging * to components. * * Generally, you only need to specify properties relevant to your test, * and everything else gets set to sensible defaults. * * The actual generation of entities is done by {@link behat_generator_base}. * There is one subclass for each component, e.g. {@link behat_core_generator} * or {@link behat_mod_quiz_generator}. To see the types of entity * that can be created for each component, look at the arrays returned * by the get_creatable_entities() method in each class. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_data_generators extends behat_base { /** * Convert legacy entity names to the new component-specific form. * * In the past, there was no support for plugins, and everything that * could be created was handled by the core generator. Now, we can * support plugins, and so some thing should probably be moved. * * For example, in the future we should probably add * 'message contacts' => 'core_message > contact'] to * this array, and move generation of message contact * from core to core_message. * * @var array old entity type => new entity type. */ protected $movedentitytypes = [ ]; /** * Creates the specified elements. * * See the class comment for an overview. * * @Given /^the following "(?P<element_string>(?:[^"]|\\")*)" exist:$/ * * @param string $entitytype The name of the type entity to add * @param TableNode $data */ public function the_following_entities_exist($entitytype, TableNode $data) { if (isset($this->movedentitytypes[$entitytype])) { $entitytype = $this->movedentitytypes[$entitytype]; } list($component, $entity) = $this->parse_entity_type($entitytype); $this->get_instance_for_component($component)->generate_items($entity, $data); } /** * Create multiple entities of one entity type. * * @Given :count :entitytype exist with the following data: * * @param string $entitytype The name of the type entity to add * @param int $count * @param TableNode $data */ public function the_following_repeated_entities_exist(string $entitytype, int $count, TableNode $data): void { $rows = $data->getRowsHash(); $tabledata = [array_keys($rows)]; for ($current = 1; $current < $count + 1; $current++) { $rowdata = []; foreach ($rows as $fieldname => $fieldtemplate) { $rowdata[$fieldname] = str_replace('[count]', $current, $fieldtemplate); } $tabledata[] = $rowdata; } if (isset($this->movedentitytypes[$entitytype])) { $entitytype = $this->movedentitytypes[$entitytype]; } list($component, $entity) = $this->parse_entity_type($entitytype); $this->get_instance_for_component($component)->generate_items($entity, new TableNode($tabledata), false); } /** * Creates the specified element. * * See the class comment for an overview. * * @Given the following :entitytype exists: * * @param string $entitytype The name of the type entity to add * @param TableNode $data */ public function the_following_entity_exists($entitytype, TableNode $data) { if (isset($this->movedentitytypes[$entitytype])) { $entitytype = $this->movedentitytypes[$entitytype]; } list($component, $entity) = $this->parse_entity_type($entitytype); $this->get_instance_for_component($component)->generate_items($entity, $data, true); } /** * Parse a full entity type like 'users' or 'mod_forum > subscription'. * * E.g. parsing 'course' gives ['core', 'course'] and * parsing 'core_message > message' gives ['core_message', 'message']. * * @param string $entitytype the entity type * @return string[] with two elements, component and entity type. */ protected function parse_entity_type(string $entitytype): array { $dividercount = substr_count($entitytype, ' > '); if ($dividercount === 0) { return ['core', $entitytype]; } else if ($dividercount === 1) { list($component, $type) = explode(' > ', $entitytype); if ($component === 'core') { throw new coding_exception('Do not specify the component "core > ..." for entity types.'); } return [$component, $type]; } else { throw new coding_exception('The entity type must be in the form ' . '"{entity-type}" for core entities, or "{component} > {entity-type}" ' . 'for entities belonging to other components. ' . 'For example "users" or "mod_forum > subscriptions".'); } } /** * Get an instance of the appropriate subclass of this class for a given component. * * @param string $component The name of the component to generate entities for. * @return behat_generator_base the subclass of this class for the requested component. */ protected function get_instance_for_component(string $component): behat_generator_base { global $CFG; // Ensure the generator class is loaded. require_once($CFG->libdir . '/behat/classes/behat_generator_base.php'); if ($component === 'core') { $lib = $CFG->libdir . '/behat/classes/behat_core_generator.php'; } else { $dir = core_component::get_component_directory($component); $lib = $dir . '/tests/generator/behat_' . $component . '_generator.php'; if (!$dir || !is_readable($lib)) { throw new coding_exception("Component {$component} does not support " . "behat generators yet. Missing {$lib}."); } } require_once($lib); // Create an instance. $componentclass = "behat_{$component}_generator"; if (!class_exists($componentclass)) { throw new PendingException($component . ' does not yet support the Behat data generator mechanism. Class ' . $componentclass . ' not found in file ' . $lib . '.'); } $instance = new $componentclass($component); return $instance; } /** * Get all entities that can be created in all components using the_following_entities_exist() * * @return array * @throws coding_exception */ public function get_all_entities(): array { global $CFG; // Ensure the generator class is loaded. require_once($CFG->libdir . '/behat/classes/behat_generator_base.php'); $componenttypes = core_component::get_component_list(); $coregenerator = $this->get_instance_for_component('core'); $pluginswithentities = ['core' => array_keys($coregenerator->get_available_generators())]; foreach ($componenttypes as $components) { foreach ($components as $component => $componentdir) { try { $plugingenerator = $this->get_instance_for_component($component); $entities = array_keys($plugingenerator->get_available_generators()); if (!empty($entities)) { $pluginswithentities[$component] = $entities; } } catch (Exception $e) { // The component has no generator, skip it. continue; } } } return $pluginswithentities; } /** * Get the required fields for a specific creatable entity. * * @param string $entitytype * @return mixed * @throws coding_exception */ public function get_entity(string $entitytype): array { [$component, $entity] = $this->parse_entity_type($entitytype); $generator = $this->get_instance_for_component($component); $entities = $generator->get_available_generators(); if (!array_key_exists($entity, $entities)) { throw new coding_exception('No generator for ' . $entity . ' in component ' . $component); } return $entities[$entity]; } } behat_hooks.php 0000644 00000102021 15151222201 0007526 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Behat hooks steps definitions. * * This methods are used by Behat CLI command. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../behat/behat_base.php'); use Behat\Testwork\Hook\Scope\BeforeSuiteScope, Behat\Testwork\Hook\Scope\AfterSuiteScope, Behat\Behat\Hook\Scope\BeforeFeatureScope, Behat\Behat\Hook\Scope\AfterFeatureScope, Behat\Behat\Hook\Scope\BeforeScenarioScope, Behat\Behat\Hook\Scope\AfterScenarioScope, Behat\Behat\Hook\Scope\BeforeStepScope, Behat\Behat\Hook\Scope\AfterStepScope, Behat\Mink\Exception\ExpectationException, Behat\Mink\Exception\DriverException, Facebook\WebDriver\Exception\UnexpectedAlertOpenException, Facebook\WebDriver\Exception\WebDriverCurlException, Facebook\WebDriver\Exception\UnknownErrorException; /** * Hooks to the behat process. * * Behat accepts hooks after and before each * suite, feature, scenario and step. * * They can not call other steps as part of their process * like regular steps definitions does. * * Throws generic Exception because they are captured by Behat. * * @package core * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_hooks extends behat_base { /** * @var For actions that should only run once. */ protected static $initprocessesfinished = false; /** @var bool Whether the first javascript scenario has been seen yet */ protected static $firstjavascriptscenarioseen = false; /** * @var bool Scenario running */ protected $scenariorunning = false; /** * Some exceptions can only be caught in a before or after step hook, * they can not be thrown there as they will provoke a framework level * failure, but we can store them here to fail the step in i_look_for_exceptions() * which result will be parsed by the framework as the last step result. * * @var ?Exception Null or the exception last step throw in the before or after hook. */ protected static $currentstepexception = null; /** * If an Exception is thrown in the BeforeScenario hook it will cause the Scenario to be skipped, and the exit code * to be non-zero triggering a potential rerun. * * To combat this the exception is stored and re-thrown when looking for exceptions. * This allows the test to instead be failed and re-run correctly. * * @var null|Exception */ protected static $currentscenarioexception = null; /** * If we are saving any kind of dump on failure we should use the same parent dir during a run. * * @var The parent dir name */ protected static $faildumpdirname = false; /** * Keeps track of time taken by feature to execute. * * @var array list of feature timings */ protected static $timings = array(); /** * Keeps track of current running suite name. * * @var string current running suite name */ protected static $runningsuite = ''; /** * @var array Array (with tag names in keys) of all tags in current scenario. */ protected static $scenariotags; /** * Gives access to moodle codebase, ensures all is ready and sets up the test lock. * * Includes config.php to use moodle codebase with $CFG->behat_* instead of $CFG->prefix and $CFG->dataroot, called * once per suite. * * @BeforeSuite * @param BeforeSuiteScope $scope scope passed by event fired before suite. */ public static function before_suite_hook(BeforeSuiteScope $scope) { global $CFG; // If behat has been initialised then no need to do this again. if (!self::is_first_scenario()) { return; } // Defined only when the behat CLI command is running, the moodle init setup process will // read this value and switch to $CFG->behat_dataroot and $CFG->behat_prefix instead of // the normal site. if (!defined('BEHAT_TEST')) { define('BEHAT_TEST', 1); } if (!defined('CLI_SCRIPT')) { define('CLI_SCRIPT', 1); } // With BEHAT_TEST we will be using $CFG->behat_* instead of $CFG->dataroot, $CFG->prefix and $CFG->wwwroot. require_once(__DIR__ . '/../../../config.php'); // Now that we are MOODLE_INTERNAL. require_once(__DIR__ . '/../../behat/classes/behat_command.php'); require_once(__DIR__ . '/../../behat/classes/behat_selectors.php'); require_once(__DIR__ . '/../../behat/classes/behat_context_helper.php'); require_once(__DIR__ . '/../../behat/classes/util.php'); require_once(__DIR__ . '/../../testing/classes/test_lock.php'); require_once(__DIR__ . '/../../testing/classes/nasty_strings.php'); // Avoids vendor/bin/behat to be executed directly without test environment enabled // to prevent undesired db & dataroot modifications, this is also checked // before each scenario (accidental user deletes) in the BeforeScenario hook. if (!behat_util::is_test_mode_enabled()) { self::log_and_stop('Behat only can run if test mode is enabled. More info in ' . behat_command::DOCS_URL); } // Reset all data, before checking for check_server_status. // If not done, then it can return apache error, while running tests. behat_util::clean_tables_updated_by_scenario_list(); behat_util::reset_all_data(); // Check if the web server is running and using same version for cli and apache. behat_util::check_server_status(); // Prevents using outdated data, upgrade script would start and tests would fail. if (!behat_util::is_test_data_updated()) { $commandpath = 'php admin/tool/behat/cli/init.php'; $message = <<<EOF Your behat test site is outdated, please run the following command from your Moodle dirroot to drop, and reinstall the Behat test site. {$commandpath} EOF; self::log_and_stop($message); } // Avoid parallel tests execution, it continues when the previous lock is released. test_lock::acquire('behat'); if (!empty($CFG->behat_faildump_path) && !is_writable($CFG->behat_faildump_path)) { self::log_and_stop( "The \$CFG->behat_faildump_path value is set to a non-writable directory ({$CFG->behat_faildump_path})." ); } // Handle interrupts on PHP7. if (extension_loaded('pcntl')) { $disabled = explode(',', ini_get('disable_functions')); if (!in_array('pcntl_signal', $disabled)) { declare(ticks = 1); } } } /** * Run final tests before running the suite. * * @BeforeSuite * @param BeforeSuiteScope $scope scope passed by event fired before suite. */ public static function before_suite_final_checks(BeforeSuiteScope $scope) { $happy = defined('BEHAT_TEST'); $happy = $happy && defined('BEHAT_SITE_RUNNING'); $happy = $happy && php_sapi_name() == 'cli'; $happy = $happy && behat_util::is_test_mode_enabled(); $happy = $happy && behat_util::is_test_site(); if (!$happy) { error_log('Behat only can modify the test database and the test dataroot!'); exit(1); } } /** * Gives access to moodle codebase, to keep track of feature start time. * * @param BeforeFeatureScope $scope scope passed by event fired before feature. * @BeforeFeature */ public static function before_feature(BeforeFeatureScope $scope) { if (!defined('BEHAT_FEATURE_TIMING_FILE')) { return; } $file = $scope->getFeature()->getFile(); self::$timings[$file] = microtime(true); } /** * Gives access to moodle codebase, to keep track of feature end time. * * @param AfterFeatureScope $scope scope passed by event fired after feature. * @AfterFeature */ public static function after_feature(AfterFeatureScope $scope) { if (!defined('BEHAT_FEATURE_TIMING_FILE')) { return; } $file = $scope->getFeature()->getFile(); self::$timings[$file] = microtime(true) - self::$timings[$file]; // Probably didn't actually run this, don't output it. if (self::$timings[$file] < 1) { unset(self::$timings[$file]); } } /** * Gives access to moodle codebase, to keep track of suite timings. * * @param AfterSuiteScope $scope scope passed by event fired after suite. * @AfterSuite */ public static function after_suite(AfterSuiteScope $scope) { if (!defined('BEHAT_FEATURE_TIMING_FILE')) { return; } $realroot = realpath(__DIR__.'/../../../').'/'; foreach (self::$timings as $k => $v) { $new = str_replace($realroot, '', $k); self::$timings[$new] = round($v, 1); unset(self::$timings[$k]); } if ($existing = @json_decode(file_get_contents(BEHAT_FEATURE_TIMING_FILE), true)) { self::$timings = array_merge($existing, self::$timings); } arsort(self::$timings); @file_put_contents(BEHAT_FEATURE_TIMING_FILE, json_encode(self::$timings, JSON_PRETTY_PRINT)); } /** * Helper function to restart the Mink session. */ protected function restart_session(): void { $session = $this->getSession(); if ($session->isStarted()) { $session->restart(); } else { $this->start_session(); } if ($this->running_javascript() && $this->getSession()->getDriver()->getWebDriverSessionId() === 'session') { throw new DriverException('Unable to create a valid session'); } } /** * Start the Session, applying any initial configuratino required. */ protected function start_session(): void { $this->getSession()->start(); $this->set_test_timeout_factor(1); } /** * Restart the session before each non-javascript scenario. * * @BeforeScenario @~javascript * @param BeforeScenarioScope $scope scope passed by event fired before scenario. */ public function before_goutte_scenarios(BeforeScenarioScope $scope) { if ($this->running_javascript()) { // A bug in the BeforeScenario filtering prevents the @~javascript filter on this hook from working // properly. // See https://github.com/Behat/Behat/issues/1235 for further information. return; } $this->restart_session(); } /** * Start the session before the first javascript scenario. * * This is treated slightly differently to try to capture when Selenium is not running at all. * * @BeforeScenario @javascript * @param BeforeScenarioScope $scope scope passed by event fired before scenario. */ public function before_first_scenario_start_session(BeforeScenarioScope $scope) { if (!self::is_first_javascript_scenario()) { // The first Scenario has started. // The `before_subsequent_scenario_start_session` function will restart the session instead. return; } $docsurl = behat_command::DOCS_URL; $driverexceptionmsg = <<<EOF The Selenium or WebDriver server is not running. You must start it to run tests that involve Javascript. See {$docsurl} for more information. The following debugging information is available: EOF; try { $this->restart_session(); } catch (WebDriverCurlException | DriverException $e) { // Thrown by WebDriver. self::log_and_stop( $driverexceptionmsg . '. ' . $e->getMessage() . "\n\n" . format_backtrace($e->getTrace(), true) ); } catch (UnknownErrorException $e) { // Generic 'I have no idea' Selenium error. Custom exception to provide more feedback about possible solutions. self::log_and_stop( $e->getMessage() . "\n\n" . format_backtrace($e->getTrace(), true) ); } } /** * Start the session before each javascript scenario. * * Note: Before the first scenario the @see before_first_scenario_start_session() function is used instead. * * @BeforeScenario @javascript * @param BeforeScenarioScope $scope scope passed by event fired before scenario. */ public function before_subsequent_scenario_start_session(BeforeScenarioScope $scope) { if (self::is_first_javascript_scenario()) { // The initial init has not yet finished. // The `before_first_scenario_start_session` function will have started the session instead. return; } self::$currentscenarioexception = null; try { $this->restart_session(); } catch (Exception $e) { self::$currentscenarioexception = $e; } } /** * Resets the test environment. * * @BeforeScenario * @param BeforeScenarioScope $scope scope passed by event fired before scenario. */ public function before_scenario_hook(BeforeScenarioScope $scope) { global $DB; if (self::$currentscenarioexception) { // A BeforeScenario hook triggered an exception and marked this test as failed. // Skip this hook as it will likely fail. return; } $suitename = $scope->getSuite()->getName(); // Register behat selectors for theme, if suite is changed. We do it for every suite change. if ($suitename !== self::$runningsuite) { self::$runningsuite = $suitename; behat_context_helper::set_environment($scope->getEnvironment()); // We need the Mink session to do it and we do it only before the first scenario. $namedpartialclass = 'behat_partial_named_selector'; $namedexactclass = 'behat_exact_named_selector'; // If override selector exist, then set it as default behat selectors class. $overrideclass = behat_config_util::get_behat_theme_selector_override_classname($suitename, 'named_partial', true); if (class_exists($overrideclass)) { $namedpartialclass = $overrideclass; } // If override selector exist, then set it as default behat selectors class. $overrideclass = behat_config_util::get_behat_theme_selector_override_classname($suitename, 'named_exact', true); if (class_exists($overrideclass)) { $namedexactclass = $overrideclass; } $this->getSession()->getSelectorsHandler()->registerSelector('named_partial', new $namedpartialclass()); $this->getSession()->getSelectorsHandler()->registerSelector('named_exact', new $namedexactclass()); // Register component named selectors. foreach (\core_component::get_component_names() as $component) { $this->register_component_selectors_for_component($component); } } // Reset $SESSION. \core\session\manager::init_empty_session(); // Ignore E_NOTICE and E_WARNING during reset, as this might be caused because of some existing process // running ajax. This will be investigated in another issue. $errorlevel = error_reporting(); error_reporting($errorlevel & ~E_NOTICE & ~E_WARNING); behat_util::reset_all_data(); error_reporting($errorlevel); if ($this->running_javascript()) { // Fetch the user agent. // This isused to choose between the SVG/Non-SVG versions of themes. $useragent = $this->getSession()->evaluateScript('return navigator.userAgent;'); \core_useragent::instance(true, $useragent); // Restore the saved themes. behat_util::restore_saved_themes(); } // Assign valid data to admin user (some generator-related code needs a valid user). $user = $DB->get_record('user', array('username' => 'admin')); \core\session\manager::set_user($user); // Set the theme if not default. if ($suitename !== "default") { set_config('theme', $suitename); } // Reset the scenariorunning variable to ensure that Step 0 occurs. $this->scenariorunning = false; // Set up the tags for current scenario. self::fetch_tags_for_scenario($scope); // If scenario requires the Moodle app to be running, set this up. if ($this->has_tag('app')) { $this->execute('behat_app::start_scenario'); return; } // Run all test with medium (1024x768) screen size, to avoid responsive problems. $this->resize_window('medium'); } /** * Mark the first Javascript Scenario as have been seen. * * @BeforeScenario * @param BeforeScenarioScope $scope scope passed by event fired before scenario. */ public function mark_first_js_scenario_as_seen(BeforeScenarioScope $scope) { self::$firstjavascriptscenarioseen = true; } /** * Hook to open the site root before the first step in the suite. * Yes, this is in a strange location and should be in the BeforeScenario hook, but failures in the test setUp lead * to the test being incorrectly marked as skipped with no way to force the test to be failed. * * @param BeforeStepScope $scope * @BeforeStep */ public function before_step(BeforeStepScope $scope) { global $CFG; if (!$this->scenariorunning) { // We need to visit / before the first step in any Scenario. // This is our Step 0. // Ideally this would be in the BeforeScenario hook, but any exception in there will lead to the test being // skipped rather than it being failed. // // We also need to check that the site returned is a Behat site. // Again, this would be better in the BeforeSuite hook, but that does not have access to the selectors in // order to perform the necessary searches. $session = $this->getSession(); $this->execute('behat_general::i_visit', ['/']); // Checking that the root path is a Moodle test site. if (self::is_first_scenario()) { $message = "The base URL ({$CFG->wwwroot}) is not a behat test site. " . 'Ensure that you started the built-in web server in the correct directory, ' . 'or that your web server is correctly set up and started.'; $this->find( "xpath", "//head/child::title[normalize-space(.)='" . behat_util::BEHATSITENAME . "']", new ExpectationException($message, $session) ); } $this->scenariorunning = true; } } /** * Sets up the tags for the current scenario. * * @param \Behat\Behat\Hook\Scope\BeforeScenarioScope $scope Scope */ protected static function fetch_tags_for_scenario(\Behat\Behat\Hook\Scope\BeforeScenarioScope $scope) { self::$scenariotags = array_flip(array_merge( $scope->getScenario()->getTags(), $scope->getFeature()->getTags() )); } /** * Gets the tags for the current scenario * * @return array Array where key is tag name and value is an integer */ public static function get_tags_for_scenario() : array { return self::$scenariotags; } /** * Wait for JS to complete before beginning interacting with the DOM. * * Executed only when running against a real browser. We wrap it * all in a try & catch to forward the exception to i_look_for_exceptions * so the exception will be at scenario level, which causes a failure, by * default would be at framework level, which will stop the execution of * the run. * * @param BeforeStepScope $scope scope passed by event fired before step. * @BeforeStep */ public function before_step_javascript(BeforeStepScope $scope) { if (self::$currentscenarioexception) { // A BeforeScenario hook triggered an exception and marked this test as failed. // Skip this hook as it will likely fail. return; } self::$currentstepexception = null; // Only run if JS. if ($this->running_javascript()) { try { $this->wait_for_pending_js(); } catch (Exception $e) { self::$currentstepexception = $e; } } } /** * Wait for JS to complete after finishing the step. * * With this we ensure that there are not AJAX calls * still in progress. * * Executed only when running against a real browser. We wrap it * all in a try & catch to forward the exception to i_look_for_exceptions * so the exception will be at scenario level, which causes a failure, by * default would be at framework level, which will stop the execution of * the run. * * @param AfterStepScope $scope scope passed by event fired after step.. * @AfterStep */ public function after_step_javascript(AfterStepScope $scope) { global $CFG, $DB; // If step is undefined then throw exception, to get failed exit code. if ($scope->getTestResult()->getResultCode() === Behat\Behat\Tester\Result\StepResult::UNDEFINED) { throw new coding_exception("Step '" . $scope->getStep()->getText() . "'' is undefined."); } $isfailed = $scope->getTestResult()->getResultCode() === Behat\Testwork\Tester\Result\TestResult::FAILED; // Abort any open transactions to prevent subsequent tests hanging. // This does the same as abort_all_db_transactions(), but doesn't call error_log() as we don't // want to see a message in the behat output. if (($scope->getTestResult() instanceof \Behat\Behat\Tester\Result\ExecutedStepResult) && $scope->getTestResult()->hasException()) { if ($DB && $DB->is_transaction_started()) { $DB->force_transaction_rollback(); } } if ($isfailed && !empty($CFG->behat_faildump_path)) { // Save the page content (html). $this->take_contentdump($scope); if ($this->running_javascript()) { // Save a screenshot. $this->take_screenshot($scope); } } if ($isfailed && !empty($CFG->behat_pause_on_fail)) { $exception = $scope->getTestResult()->getException(); $message = "<colour:lightRed>Scenario failed. "; $message .= "<colour:lightYellow>Paused for inspection. Press <colour:lightRed>Enter/Return<colour:lightYellow> to continue.<newline>"; $message .= "<colour:lightRed>Exception follows:<newline>"; $message .= trim($exception->getMessage()); behat_util::pause($this->getSession(), $message); } // Only run if JS. if (!$this->running_javascript()) { return; } try { $this->wait_for_pending_js(); self::$currentstepexception = null; } catch (UnexpectedAlertOpenException $e) { self::$currentstepexception = $e; // Accepting the alert so the framework can continue properly running // the following scenarios. Some browsers already closes the alert, so // wrapping in a try & catch. try { $this->getSession()->getDriver()->getWebDriver()->switchTo()->alert()->accept(); } catch (Exception $e) { // Catching the generic one as we never know how drivers reacts here. } } catch (Exception $e) { self::$currentstepexception = $e; } } /** * Reset the session between each scenario. * * @param AfterScenarioScope $scope scope passed by event fired after scenario. * @AfterScenario */ public function reset_webdriver_between_scenarios(AfterScenarioScope $scope) { try { $this->getSession()->stop(); } catch (Exception $e) { $error = <<<EOF Error while stopping WebDriver: %s (%d) '%s' Attempting to continue with test run. Stacktrace follows: %s EOF; error_log(sprintf( $error, get_class($e), $e->getCode(), $e->getMessage(), format_backtrace($e->getTrace(), true) )); } } /** * Getter for self::$faildumpdirname * * @return string */ protected function get_run_faildump_dir() { return self::$faildumpdirname; } /** * Take screenshot when a step fails. * * @throws Exception * @param AfterStepScope $scope scope passed by event after step. */ protected function take_screenshot(AfterStepScope $scope) { // Goutte can't save screenshots. if (!$this->running_javascript()) { return false; } // Some drivers (e.g. chromedriver) may throw an exception while trying to take a screenshot. If this isn't handled, // the behat run dies. We don't want to lose the information about the failure that triggered the screenshot, // so let's log the exception message to a file (to explain why there's no screenshot) and allow the run to continue, // handling the failure as normal. try { list ($dir, $filename) = $this->get_faildump_filename($scope, 'png'); $this->saveScreenshot($filename, $dir); } catch (Exception $e) { // Catching all exceptions as we don't know what the driver might throw. list ($dir, $filename) = $this->get_faildump_filename($scope, 'txt'); $message = "Could not save screenshot due to an error\n" . $e->getMessage(); file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $message); } } /** * Take a dump of the page content when a step fails. * * @throws Exception * @param AfterStepScope $scope scope passed by event after step. */ protected function take_contentdump(AfterStepScope $scope) { list ($dir, $filename) = $this->get_faildump_filename($scope, 'html'); try { // Driver may throw an exception during getContent(), so do it first to avoid getting an empty file. $content = $this->getSession()->getPage()->getContent(); } catch (Exception $e) { // Catching all exceptions as we don't know what the driver might throw. $content = "Could not save contentdump due to an error\n" . $e->getMessage(); } file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $content); } /** * Determine the full pathname to store a failure-related dump. * * This is used for content such as the DOM, and screenshots. * * @param AfterStepScope $scope scope passed by event after step. * @param String $filetype The file suffix to use. Limited to 4 chars. */ protected function get_faildump_filename(AfterStepScope $scope, $filetype) { global $CFG; // All the contentdumps should be in the same parent dir. if (!$faildumpdir = self::get_run_faildump_dir()) { $faildumpdir = self::$faildumpdirname = date('Ymd_His'); $dir = $CFG->behat_faildump_path . DIRECTORY_SEPARATOR . $faildumpdir; if (!is_dir($dir) && !mkdir($dir, $CFG->directorypermissions, true)) { // It shouldn't, we already checked that the directory is writable. throw new Exception('No directories can be created inside $CFG->behat_faildump_path, check the directory permissions.'); } } else { // We will always need to know the full path. $dir = $CFG->behat_faildump_path . DIRECTORY_SEPARATOR . $faildumpdir; } // The scenario title + the failed step text. // We want a i-am-the-scenario-title_i-am-the-failed-step.$filetype format. $filename = $scope->getFeature()->getTitle() . '_' . $scope->getStep()->getText(); // As file name is limited to 255 characters. Leaving 5 chars for line number and 4 chars for the file. // extension as we allow .png for images and .html for DOM contents. $filenamelen = 245; // Suffix suite name to faildump file, if it's not default suite. $suitename = $scope->getSuite()->getName(); if ($suitename != 'default') { $suitename = '_' . $suitename; $filenamelen = $filenamelen - strlen($suitename); } else { // No need to append suite name for default. $suitename = ''; } $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename); $filename = substr($filename, 0, $filenamelen) . $suitename . '_' . $scope->getStep()->getLine() . '.' . $filetype; return array($dir, $filename); } /** * Internal step definition to find exceptions, debugging() messages and PHP debug messages. * * Part of behat_hooks class as is part of the testing framework, is auto-executed * after each step so no features will splicitly use it. * * @Given /^I look for exceptions$/ * @throw Exception Unknown type, depending on what we caught in the hook or basic \Exception. * @see Moodle\BehatExtension\EventDispatcher\Tester\ChainedStepTester */ public function i_look_for_exceptions() { // If the scenario already failed in a hook throw the exception. if (!is_null(self::$currentscenarioexception)) { throw self::$currentscenarioexception; } // If the step already failed in a hook throw the exception. if (!is_null(self::$currentstepexception)) { throw self::$currentstepexception; } $this->look_for_exceptions(); } /** * Returns whether the first scenario of the suite is running * * @return bool */ protected static function is_first_scenario() { return !(self::$initprocessesfinished); } /** * Returns whether the first scenario of the suite is running * * @return bool */ protected static function is_first_javascript_scenario(): bool { return !self::$firstjavascriptscenarioseen; } /** * Register a set of component selectors. * * @param string $component */ public function register_component_selectors_for_component(string $component): void { $context = behat_context_helper::get_component_context($component); if ($context === null) { return; } $namedpartial = $this->getSession()->getSelectorsHandler()->getSelector('named_partial'); $namedexact = $this->getSession()->getSelectorsHandler()->getSelector('named_exact'); // Replacements must come before selectors as they are used in the selectors. foreach ($context->get_named_replacements() as $replacement) { $namedpartial->register_replacement($component, $replacement); $namedexact->register_replacement($component, $replacement); } foreach ($context->get_partial_named_selectors() as $selector) { $namedpartial->register_component_selector($component, $selector); } foreach ($context->get_exact_named_selectors() as $selector) { $namedexact->register_component_selector($component, $selector); } } /** * Mark the first step as having been completed. * * This must be the last BeforeStep hook in the setup. * * @param BeforeStepScope $scope * @BeforeStep */ public function first_step_setup_complete(BeforeStepScope $scope): void { self::$initprocessesfinished = true; } /** * Log a notification, and then exit. * * @param string $message The content to dispaly */ protected static function log_and_stop(string $message): void { error_log($message); exit(1); } } move_blocks.feature 0000644 00000003102 15151236107 0010421 0 ustar 00 @core @core_block Feature: Block region moving In order to configure blocks appearance As a teacher I need to modify block region for the page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | name | template | | survey | C1 | Test survey name | 4 | | book | C1 | Test book name | | And the following "mod_book > chapter" exists: | book | Test book name | | title | Book title | | content | Book content test test | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Comments" block And I configure the "Comments" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" Scenario: Block settings can be modified so that a block can be moved When I follow "Test book name" And I configure the "Comments" block And I set the following fields to these values: | Region | Right | And I press "Save changes" And I should see "Comments" in the "//*[@id='region-post' or @id='block-region-side-post']" "xpath_element" add_blocks.feature 0000644 00000001643 15151236107 0010213 0 ustar 00 @core @core_block Feature: Add blocks In order to add more functionality to pages As a teacher I need to add blocks to pages Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | student2 | Student | 2 | student2@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student2 | C1 | student | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I add the "Blog menu" block Then I should see "View my entries about this course" @javascript Scenario: Add a block to a course with Javascript enabled Scenario: Add a block to a course with Javascript disabled hidden_block_region.feature 0000644 00000004125 15151236107 0012074 0 ustar 00 @core @core_block Feature: Show hidden blocks in a docked block region when editing In order to edit blocks in a hidden region As a teacher I need to be able to see the blocks when editing is on Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I add the "Search forums" block And I add the "Latest announcements" block And I add the "Upcoming events" block And I add the "Recent activity" block # Hide all the blocks in the non-default region And I configure the "Search forums" block And I set the following fields to these values: | Visible | No | And I click on "Save changes" "button" And I configure the "Latest announcements" block And I set the following fields to these values: | Visible | No | And I click on "Save changes" "button" And I configure the "Upcoming events" block And I set the following fields to these values: | Visible | No | And I click on "Save changes" "button" And I configure the "Recent activity" block And I set the following fields to these values: | Visible | No | When I click on "Save changes" "button" # Editing is on so they should be visible Then I should see "Search forums" And I should see "Latest announcements" And I should see "Upcoming events" And I should see "Recent activity" And I turn editing mode off # Editing is off, so they should no longer be visible And I should not see "Search forums" And I should not see "Latest announcements" And I should not see "Upcoming events" And I should not see "Recent activity" @javascript Scenario: Check that a region with only hidden blocks is not docked in editing mode (javascript enabled) Scenario: Check that a region with only hidden blocks is not docked in editing mode (javascript disabled) delete_block.feature 0000644 00000002464 15151236107 0010544 0 ustar 00 @core @core_block Feature: Block removal via modal In order to remove blocks As a teacher I need to use a modal to confirm the block to delete Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I add the "Search forums" block And "Search forums" "block" should exist @javascript Scenario: Removing a block via modal should remove the block on the page Given I open the "Search forums" blocks action menu When I click on "Delete Search forums block" "link" in the "Search forums" "block" Then "Delete block?" "dialogue" should exist And I click on "Delete" "button" in the "Delete block?" "dialogue" And I wait to be redirected And "Search forums" "block" should not exist @javascript Scenario: Cancel removing a block via modal should retain the block on the page Given I open the "Search forums" blocks action menu When I click on "Delete Search forums block" "link" in the "Search forums" "block" Then "Delete block?" "dialogue" should exist And I click on "Cancel" "button" in the "Delete block?" "dialogue" And I should not see "Delete block?" And "Search forums" "block" should exist configure_block_throughout_site.feature 0000644 00000006063 15151236107 0014576 0 ustar 00 @core @core_block Feature: Add and configure blocks throughout the site In order to maintain some patterns across all the site As a manager I need to set and configure blocks throughout the site Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | manager1 | Manager | 1 | manager1@example.com | | teacher1 | teacher | 1 | teacher@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "system role assigns" exist: | user | course | role | | manager1 | Acceptance test site | manager | # Allow at least one role assignment in the block context: And I log in as "admin" And I navigate to "Users > Permissions > Define roles" in site administration And I follow "Edit Non-editing teacher role" And I set the following fields to these values: | Block | 1 | And I press "Save changes" And I log out Scenario: Add and configure a block throughtout the site Given I log in as "manager1" And I am on site homepage And I turn editing mode on And I add the "Comments" block And I configure the "Comments" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" When I am on "Course 1" course homepage Then I should see "Comments" in the "Comments" "block" And I should see "Save comment" in the "Comments" "block" And I am on site homepage And I configure the "Comments" block And I set the following fields to these values: | Default weight | -10 (first) | And I press "Save changes" And I am on "Course 1" course homepage # The first block matching the pattern should be top-left block And I should see "Comments" in the "//*[@id='region-pre' or @id='block-region-side-pre']/descendant::*[contains(concat(' ', normalize-space(@class), ' '), ' block_comments ')]" "xpath_element" Scenario: Blocks on the dashboard page can have roles assigned to them Given I log in as "manager1" When I turn editing mode on Then I should see "Assign roles in Recently accessed items block" Scenario: Blocks on courses can have roles assigned to them Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Search forums" block Then I should see "Assign roles in Search forums block" @javascript Scenario: Blocks can safely be customised Given I log in as "admin" And I am on homepage And I turn editing mode on And I add the "Text" block And I configure the "(new text block)" block And I set the following fields to these values: | Text block title | Foo " onload="document.getElementsByTagName('body')[0].remove()" alt=" | | Content | Example | When I press "Save changes" Then I should see "Example" hide_blocks.feature 0000644 00000002152 15151236107 0010370 0 ustar 00 @core @core_block Feature: Block visibility In order to configure blocks visibility As a teacher I need to show and hide blocks on a page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on @javascript Scenario: Hiding all blocks on the page should remove the column they're in Given I add the "Search forums" block And I open the "Search forums" blocks action menu And I click on "Configure Search forums block" "link" in the "Search forums" "block" And I set the field "Region" to "Right" And I press "Save changes" And I turn editing mode off And ".empty-region-side-post" "css_element" should not exist in the "body" "css_element" And I turn editing mode on And I open the "Search forums" blocks action menu And I click on "Hide Search forums block" "link" in the "Search forums" "block" And I turn editing mode off And ".empty-region-side-post" "css_element" should exist in the "body" "css_element" add_blocks_overridden_disabled.feature 0000644 00000012754 15151236107 0014270 0 ustar 00 @block @core_block @javascript Feature: Add a block when main feature is disabled In order to add a block to my course As a teacher Some blocks should be only added to courses if the main feature they are based on is enabled. Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And I am on the "C1" "course" page logged in as "admin" Scenario Outline: The block is displayed even when main feature is disabled Given the following config values are set as admin: | <settingname1> | 1 | <settingplugin1> | And I turn editing mode on And I add the "<blockname>" block When the following config values are set as admin: | <settingname1> | 0 | <settingplugin1> | Then I should see "<blockname>" Examples: | blockname | settingname1 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | Blog menu | enableblogs | | | Recent blog entries | enableblogs | | | Comments | usecomments | | | Course completion status | enablecompletion | | | Global search | enableglobalsearch | | | Latest badges | enablebadges | | | Tags | usetags | | | Learning plans | enabled | core_competency | Scenario Outline: The block is displayed even when main feature is disabled (2 settings) Given the following config values are set as admin: | <settingname1> | 1 | | <settingname2> | 1 | And I turn editing mode on And I add the "<blockname>" block When the following config values are set as admin: | <settingname1> | 0 | | <settingname2> | 0 | Then I should see "<blockname>" Examples: | blockname | settingname1 | settingname2 | | Blog tags | enableblogs | usetags | Scenario Outline: The block can be removed even when main feature is disabled Given the following config values are set as admin: | <settingname1> | 1 | <settingplugin1> | And I turn editing mode on And I add the "<blockname>" block And I open the "<blockname>" blocks action menu And I click on "Delete <blockname> block" "link" in the "<blockname>" "block" And "Delete block?" "dialogue" should exist And I click on "Cancel" "button" in the "Delete block?" "dialogue" And I should see "<blockname>" When the following config values are set as admin: | <settingname1> | 0 | <settingplugin1> | And I open the "<blockname>" blocks action menu And I click on "Delete <blockname> block" "link" in the "<blockname>" "block" And "Delete block?" "dialogue" should exist And I click on "Delete" "button" in the "Delete block?" "dialogue" Then I should not see "<blockname>" Examples: | blockname | settingname1 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | Blog menu | enableblogs | | | Recent blog entries | enableblogs | | | Comments | usecomments | | | Course completion status | enablecompletion | | | Global search | enableglobalsearch | | | Latest badges | enablebadges | | | Tags | usetags | | | Learning plans | enabled | core_competency | Scenario Outline: The block can be removed even when main feature is disabled (2 settings) Given the following config values are set as admin: | <settingname1> | 1 | | <settingname2> | 1 | And I turn editing mode on And I add the "<blockname>" block And I open the "<blockname>" blocks action menu And I click on "Delete <blockname> block" "link" in the "<blockname>" "block" And "Delete block?" "dialogue" should exist And I click on "Cancel" "button" in the "Delete block?" "dialogue" And I should see "<blockname>" When the following config values are set as admin: | <settingname1> | 0 | | <settingname2> | 0 | And I open the "<blockname>" blocks action menu And I click on "Delete <blockname> block" "link" in the "<blockname>" "block" And "Delete block?" "dialogue" should exist And I click on "Delete" "button" in the "Delete block?" "dialogue" Then I should not see "<blockname>" Examples: | blockname | settingname1 | settingname2 | | Blog tags | enableblogs | usetags | add_blocks_overridden.feature 0000644 00000007414 15151236107 0012436 0 ustar 00 @block @core_block @javascript @addablocklink Feature: Add a block when main feature is enabled In order to add a block to my course As a teacher Some blocks should be only added to courses if the main feature they are based on is enabled. Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And I am on the "C1" "course" page logged in as "admin" Scenario Outline: The block can be added when main feature is enabled Given the following config values are set as admin: | <settingname1> | 1 | <settingplugin1> | | <settingname2> | 1 | | And I turn editing mode on When I click on "Add a block" "link" Then I should see "<blockname>" Examples: | blockname | settingname1 | settingname2 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | | Blog menu | enableblogs | | | | Recent blog entries | enableblogs | | | | Blog tags | enableblogs | usetags | | | Comments | usecomments | | | | Course completion status | enablecompletion | | | | Global search | enableglobalsearch | | | | Latest badges | enablebadges | | | | Tags | usetags | | | | Learning plans | enabled | | core_competency | Scenario Outline: The block cannot be added when main feature is disabled Given the following config values are set as admin: | <settingname1> | 0 | <settingplugin1> | | <settingname2> | 0 | | And I turn editing mode on When I click on "Add a block" "link" Then I should not see "<blockname>" Examples: | blockname | settingname1 | settingname2 | settingplugin1 | | Accessibility review | enableaccessibilitytools | | | | Blog menu | enableblogs | | | | Recent blog entries | enableblogs | | | | Blog tags | enableblogs | usetags | | | Comments | usecomments | | | | Course completion status | enablecompletion | | | | Global search | enableglobalsearch | | | | Latest badges | enablebadges | | | | Tags | usetags | | | | Learning plans | enabled | | core_competency | manage_blocks.feature 0000644 00000004425 15151236107 0010714 0 ustar 00 @core @core_block Feature: Block appearances In order to configure blocks appearance As a teacher I need to add and modify block configuration for the page Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | teacher | 1 | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | course | idnumber | template | | survey | Test survey name | C1 | survey1 | 4 | | book | Test book name | C1 | book1 | | And the following "mod_book > chapter" exists: | book | Test book name | | title | Book title | | content | Book content test test | And I am on the "Course 1" course page logged in as teacher1 And I turn editing mode on And I add the "Comments" block And I configure the "Comments" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" Scenario: Block settings can be modified so that a block apprears on any page When I click on "Test survey name" "link" in the "region-main" "region" Then I should see "Comments" in the "Comments" "block" And I am on "Course 1" course homepage And I configure the "Comments" block And I set the following fields to these values: | Display on page types | Any course page | And I press "Save changes" And I turn editing mode off And I click on "Test survey name" "link" in the "region-main" "region" And I should not see "Comments" Scenario: Block settings can be modified so that a block can be hidden When I click on "Test book name" "link" in the "region-main" "region" And I configure the "Comments" block And I set the following fields to these values: | Visible | No | And I press "Save changes" And I am on "Course 1" course homepage with editing mode off And I click on "Test book name" "link" in the "region-main" "region" Then I should not see "Comments" restrict_available_blocks.feature 0000644 00000003131 15151236107 0013314 0 ustar 00 @core @core_block Feature: Allowed blocks controls In order to prevent the use of some blocks As an admin I need to restrict some blocks to be used in courses Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Blocks can be added with the default permissions Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add the "Course completion status" block And I add the "Activities" block Then I should see "Activities" in the "Activities" "block" And I should see "Course completion status" in the "Course completion status" "block" Scenario: Blocks can not be added when the admin restricts the permissions Given I log in as "admin" And I set the following system permissions of "Teacher" role: | block/activity_modules:addinstance | Prohibit | And I am on the "Course 1" "permissions" page And I override the system permissions of "Teacher" role with: | block/completionstatus:addinstance | Prohibit | And I log out When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Then the add block selector should not contain "Activities" block And the add block selector should not contain "Course completion status" block return_block_original_state.feature 0000644 00000003753 15151236107 0013707 0 ustar 00 @core @core_block Feature: The context of a block can always be returned to it's original state. In order to revert actions when configuring blocks As an admin I need to be able to return the block to original state Scenario: Add and configure a block to display on every page and revert back Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "activities" exist: | activity | name | intro | course | section | idnumber | | assign | Assignment1 | Description | C1 | 1 | assign1 | | assign | Assignment2 | Description | C1 | 1 | assign1 | And I log in as "admin" When I am on "Course 1" course homepage with editing mode on And I add the "Tags" block Then I should see "Tags" in the "Tags" "block" And I navigate to course participants And I configure the "Tags" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" And I am on "Course 1" course homepage And I follow "Assignment1" And I configure the "Tags" block And I set the following fields to these values: | Display on page types | Any assignment module page | And I press "Save changes" And I should see "Tags" in the "Tags" "block" And I am on "Course 1" course homepage And "Tags" "block" should not exist And I navigate to course participants And "Tags" "block" should not exist And I am on "Course 1" course homepage And I follow "Assignment2" And I should see "Tags" in the "Tags" "block" And I configure the "Tags" block And I set the following fields to these values: | Display on page types | Any page | And I press "Save changes" And I am on "Course 1" course homepage And I should see "Tags" in the "Tags" "block" And I navigate to course participants And I should see "Tags" in the "Tags" "block" behat_blocks.php 0000644 00000014333 15151236107 0007702 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Steps definitions related with blocks. * * @package core_block * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; require_once(__DIR__ . '/../../../lib/behat/behat_base.php'); /** * Blocks management steps definitions. * * @package core_block * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_blocks extends behat_base { /** * Adds the selected block. Editing mode must be previously enabled. * * @Given /^I add the "(?P<block_name_string>(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function i_add_the_block($blockname) { $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on_in_the', [$addblock, 'link_exact', '.add_block_button', 'css_element']); if (!$this->running_javascript()) { $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']); } else { $this->execute('behat_general::i_click_on_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); } } /** * Adds the selected block if it is not already present. Editing mode must be previously enabled. * * @Given /^I add the "(?P<block_name_string>(?:[^"]|\\")*)" block if not present$/ * @param string $blockname */ public function i_add_the_block_if_not_present($blockname) { try { $this->get_text_selector_node('block', $blockname); } catch (ElementNotFoundException $e) { $this->execute('behat_blocks::i_add_the_block', [$blockname]); } } /** * Opens a block's actions menu if it is not already opened. * * @Given /^I open the "(?P<block_name_string>(?:[^"]|\\")*)" blocks action menu$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $blockname */ public function i_open_the_blocks_action_menu($blockname) { if (!$this->running_javascript()) { // Action menu does not need to be open if Javascript is off. return; } // If it is already opened we do nothing. $blocknode = $this->get_text_selector_node('block', $blockname); if ($blocknode->hasClass('action-menu-shown')) { return; } $this->execute('behat_general::i_click_on_in_the', array("a[data-toggle='dropdown']", "css_element", $this->escape($blockname), "block") ); } /** * Clicks on Configure block for specified block. Page must be in editing mode. * * Argument block_name may be either the name of the block or CSS class of the block. * * @Given /^I configure the "(?P<block_name_string>(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function i_configure_the_block($blockname) { // Note that since $blockname may be either block name or CSS class, we can not use the exact label of "Configure" link. $this->execute("behat_blocks::i_open_the_blocks_action_menu", $this->escape($blockname)); $this->execute('behat_general::i_click_on_in_the', array("Configure", "link", $this->escape($blockname), "block") ); } /** * Ensures that block can be added to the page but does not actually add it. * * @Then /^the add block selector should contain "(?P<block_name_string>(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function the_add_block_selector_should_contain_block($blockname) { $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on', [$addblock, 'link_exact']); $cancelstr = get_string('cancel'); if (!$this->running_javascript()) { $this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']); } else { $this->execute('behat_general::should_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']); } } /** * Ensures that block can not be added to the page. * * @Then /^the add block selector should not contain "(?P<block_name_string>(?:[^"]|\\")*)" block$/ * @param string $blockname */ public function the_add_block_selector_should_not_contain_block($blockname) { $addblock = get_string('addblock'); $this->execute('behat_general::i_click_on', [$addblock, 'link_exact']); $cancelstr = get_string('cancel'); if (!$this->running_javascript()) { $this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', '#region-main', 'css_element']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'link_exact', '#region-main', 'css_element']); } else { $this->execute('behat_general::should_not_exist_in_the', [$blockname, 'link_exact', $addblock, 'dialogue']); $this->execute('behat_general::i_click_on_in_the', [$cancelstr, 'button', $addblock, 'dialogue']); } } } siteadmin_tour_breadcrumbs.feature 0000644 00000003315 15151252313 0013520 0 ustar 00 @tool @tool_usertours @javascript Feature: Verify the breadcrumbs in user tours site administration pages Whenever I navigate to user tours page in site administration As an admin The breadcrumbs should be visible Background: Given I log in as "admin" Scenario: Verify the breadcrumbs in user tours, expand to explore, next step, create and import pages as admin Given I navigate to "Appearance > User tours" in site administration And I click on "Block drawer" "link" And "Block drawer" "text" should exist in the ".breadcrumb" "css_element" And "User tours" "link" should exist in the ".breadcrumb" "css_element" When I click on "Expand to explore" "link" Then "Expand to explore" "text" should exist in the ".breadcrumb" "css_element" And "Block drawer" "link" should exist in the ".breadcrumb" "css_element" And "User tours" "link" should exist in the ".breadcrumb" "css_element" And I press "Cancel" And I click on "New step" "link" And "New step" "text" should exist in the ".breadcrumb" "css_element" And "Block drawer" "link" should exist in the ".breadcrumb" "css_element" And "User tours" "link" should exist in the ".breadcrumb" "css_element" And I press "Cancel" And I navigate to "Appearance > User tours" in site administration And I click on "Create a new tour" "link" And "Create a new tour" "text" should exist in the ".breadcrumb" "css_element" And "User tours" "link" should exist in the ".breadcrumb" "css_element" And I press "Cancel" And I click on "Import tour" "link" And "Import tour" "text" should exist in the ".breadcrumb" "css_element" And "User tours" "link" should exist in the ".breadcrumb" "css_element" create_tour.feature 0000644 00000022752 15151252313 0010443 0 ustar 00 @tool @tool_usertours Feature: Add a new user tour In order to help users learn of new features As an administrator I need to create a user tour @javascript Scenario: Add a new tour Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual | And I add steps to the "First tour" tour: | targettype | targetvalue_block | Title | id_content | Content type | | Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | Manual | | Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | Manual | And I add steps to the "First tour" tour: | targettype | targetvalue_selector | Title | id_content | Content type | | Selector | .usermenu | User menu | This is your personal user menu. You'll find your personal preferences and your user profile here. | Manual | When I am on homepage Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "This is the Timeline. All of your upcoming activities can be found here" And I should not see "This is the Calendar. All of your assignments and due dates can be found here" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "This is the Calendar. All of your assignments and due dates can be found here" And I should not see "This area shows you what's happening in some of your courses" And I click on "Skip tour" "button" in the "[data-role='flexitour-step']" "css_element" And I should not see "This area shows you what's happening in some of your courses" And I am on homepage And I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" And I should not see "This area shows you what's happening in some of your courses" And I follow "Reset user tour on this page" And I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" @javascript Scenario: A hidden tour should not be visible Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /my/% | | Tour is enabled | 0 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual | When I am on homepage Then I should not see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" @javascript Scenario: Tour visibility can be toggled Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /my/% | | Tour is enabled | 0 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual | And I open the User tour settings page When I click on "Enable" "link" in the "My first tour" "table_row" And I am on homepage Then I should see "Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful" @javascript Scenario: Display step numbers was enabled Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | Steps tour | | Description | My steps tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | Display step numbers | 1 | | End tour button's label | Sample end label | And I add steps to the "Steps tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | First step of the Tour | Manual | And I add steps to the "Steps tour" tour: | targettype | targetvalue_block | Title | id_content | Content type | | Block | Timeline | Timeline | Second step of the Tour | Manual | | Block | Calendar | Calendar | Third step of the Tour | Manual | When I am on homepage Then I should see "First step of the Tour" And I should see "Next (1/3)" And I should not see "End tour" And I should not see "Sample end label" And "Skip tour" "button" should exist in the "[data-role='flexitour-step']" "css_element" And I click on "Next (1/3)" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "Second step of the Tour" And I should see "Next (2/3)" And I click on "Next (2/3)" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "Third step of the Tour" And I should not see "Next (3/3)" And I should not see "Skip tour" And "Sample end label" "button" should exist in the "[data-role='flexitour-step']" "css_element" @javascript Scenario: Display step numbers was disabled Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | Steps tour | | Description | My steps tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | Display step numbers | 0 | And I add steps to the "Steps tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | First step of the Tour | Manual | And I add steps to the "Steps tour" tour: | targettype | targetvalue_block | Title | id_content | Content type | | Block | Timeline | Timeline | Second step of the Tour | Manual | | Block | Calendar | Calendar | Third step of the Tour | Manual | When I am on homepage Then I should see "First step of the Tour" And I should see "Next" And I should not see "Next (1/3)" And I click on "Next" "button" in the "[data-role='flexitour-step']" "css_element" And I should see "Second step of the Tour" And I should see "Next" And I should not see "Next (2/3)" @javascript Scenario: Single step tour with display step numbers was enable Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | Steps tour | | Description | My steps tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | Display step numbers | 1 | And I add steps to the "Steps tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | This is a single step tour | Manual | When I am on homepage Then I should see "This is a single step tour" And I should not see "Next (1/1)" tour_content_type.feature 0000644 00000011313 15151252313 0011702 0 ustar 00 @tool @tool_usertours Feature: Apply content type to a tour In order to give more content to a tour As an administrator I need to change the content type of the user tour Background: Given I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | And I add a new user tour with: | Name | tour_activityinfo_activity_student_title,tool_usertours | | Description | tour_activityinfo_activity_student_content,tool_usertours | | Apply to URL match | /my/% | | Tour is enabled | 0 | @javascript Scenario: User can choose the the content type of the tour step Given I open the User tour settings page And I click on "View" "link" in the "My first tour" "table_row" When I click on "New step" "link" Then "Content type" "select" should exist And the "Content type" select box should contain "Language string ID" And the "Content type" select box should contain "Manual" And I select "Language string ID" from the "Content type" singleselect And I should see " Language string ID" And "#fgroup_id_contenthtmlgrp" "css_element" should not be visible And I select "Manual" from the "Content type" singleselect And "#fgroup_id_contenthtmlgrp" "css_element" should be visible And I should not see "Language string ID" in the "#fitem_id_contentlangstring" "css_element" @javascript Scenario: Create a new step with Moodle Language content type Given I open the User tour settings page And I click on "View" "link" in the "My first tour" "table_row" And I click on "New step" "link" And I set the field "Title" to "tour_activityinfo_course_teacher_title,tool_usertours" And I select "Language string ID" from the "Content type" singleselect And I set the field "Language string ID" to "tour_activityinfo_course_teacher_content_abc,tool_usertours" When I press "Save changes" Then I should see "Invalid language string ID" And I set the field "Language string ID" to "tour_activityinfo_course_teacher_content,tool_usertours" And I press "Save changes" And I should see "New: Activity information" And I should see "New course settings 'Show completion conditions' and 'Show activity dates' enable you to choose whether activity completion conditions (if set) and/or dates are displayed for students on the course page." And I click on "Edit" "link" in the "New: Activity information" "table_row" And I should see "Editing \"New: Activity information\"" And the field "Title" matches value "tour_activityinfo_course_teacher_title,tool_usertours" And the field "Language string ID" matches value "tour_activityinfo_course_teacher_content,tool_usertours" @javascript Scenario: Create a new step with manual content type Given I open the User tour settings page And I click on "View" "link" in the "My first tour" "table_row" And I click on "New step" "link" And I set the field "Title" to "tour_activityinfo_course_teacher_title,tool_usertours" And I select "Manual" from the "Content type" singleselect And I set the field "id_content" to "<p><strong>Test content</strong></p>" And I press "Save changes" And I should see "New: Activity information" And I should see "Test content" And I click on "Edit" "link" in the "New: Activity information" "table_row" And I should see "Editing \"New: Activity information\"" And I should not see "Language string ID" in the "#fitem_id_contentlangstring" "css_element" And the field "Title" matches value "tour_activityinfo_course_teacher_title,tool_usertours" And the field "id_content" matches value "<p><strong>Test content</strong></p>" @javascript Scenario: Tour name and description can be translatable Given I open the User tour settings page And I should see "New: Activity information" And I should see "Activity dates plus what to do to complete the activity are shown on the activity page." When I click on "View" "link" in the "New: Activity information" "table_row" Then I should see "New: Activity information" And I should see "This is the 'New: Activity information' tour. It applies to the path '/my/%'." And I click on "edit the tour defaults" "link" And I should see "New: Activity information" And the field "Name" matches value "tour_activityinfo_activity_student_title,tool_usertours" And the field "Description" matches value "tour_activityinfo_activity_student_content,tool_usertours" tour_navigation.feature 0000644 00000011524 15151252313 0011332 0 ustar 00 @tool @tool_usertours Feature: Steps can be navigated within a tour In order to use a tour effectively As a user I can navigate its steps @javascript Scenario: Clicking on items in the page should not end the tour Given I log in as "admin" And I add a new user tour with: | Name | Calendar tour | | Description | Calendar tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | And I add steps to the "Calendar tour" tour: | targettype | Block | Title | id_content | Content type | | Block | Calendar | Calendar events | This is the calendar block | Manual | And I change window size to "large" And I follow "Dashboard" And I wait until the page is ready And I should see "This is the calendar block" When I click on ".block_calendar_month .calendar-controls .next" "css_element" And I wait until the page is ready Then I should see "Calendar events" @javascript Scenario: End tour button text for one step tours Given I log in as "admin" And I add a new user tour with: | Name | Calendar tour | | Description | Calendar tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | And I add steps to the "Calendar tour" tour: | targettype | Block | Title | id_content | Content type | | Block | Calendar | Calendar events | This is the calendar block | Manual | And I change window size to "large" And I follow "Dashboard" And I wait until the page is ready And I should see "This is the calendar block" Then I should see "Got it" @javascript Scenario: End tour button text for multiple step tours Given I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual | And I add steps to the "First tour" tour: | targettype | targetvalue_block | Title | id_content | Content type | | Block | Timeline | Timeline | This is the Timeline. All of your upcoming activities can be found here | Manual | | Block | Calendar | Calendar | This is the Calendar. All of your assignments and due dates can be found here | Manual | When I am on homepage Then I should see "Skip tour" And I should see "Next (1/3)" And I click on "Next (1/3)" "button" in the "Welcome" "dialogue" And I should see "Skip tour" And I click on "Next (2/3)" "button" in the "Timeline" "dialogue" And I should see "End tour" @javascript Scenario: Customised 'end tour' button text for one step tours Given I log in as "admin" And I add a new user tour with: | Name | Calendar tour | | Description | Calendar tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | End tour button's label | CustomText | And I add steps to the "Calendar tour" tour: | targettype | Block | Title | id_content | Content type | | Block | Calendar | Calendar events | This is the calendar block | Manual | And I change window size to "large" And I follow "Dashboard" And I wait until the page is ready And I should see "This is the calendar block" Then I should see "CustomText" @javascript Scenario: Customised 'end tour' button text for one step tours can be translatable Given I log in as "admin" And I add a new user tour with: | Name | Calendar tour | | Description | Calendar tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | End tour button's label | exporttour,tool_usertours | And I add steps to the "Calendar tour" tour: | targettype | Block | Title | id_content | Content type | | Block | Calendar | Calendar events | This is the calendar block | Manual | And I change window size to "large" And I follow "Dashboard" And I wait until the page is ready And I should see "This is the calendar block" Then I should see "Export tour" tour_filter.feature 0000644 00000023675 15151252313 0010472 0 ustar 00 @tool @tool_usertours Feature: Apply tour filters to a tour In order to give more directed tours As an administrator I need to create a user tour with filters applied @javascript Scenario: Add a tour for a specific role Given the following "courses" exist: | fullname | shortname | format | enablecompletion | | Course 1 | C1 | topics | 1 | And the following "users" exist: | username | | editor1 | | teacher1 | | student1 | And the following "course enrolments" exist: | user | course | role | | editor1 | C1 | editingteacher | | teacher1 | C1 | teacher | | student1 | C1 | student | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /course/view.php% | | Tour is enabled | 1 | | Role | Student,Non-editing teacher | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your course tour. | Manual | And I log out And I log in as "editor1" When I am on "Course 1" course homepage Then I should not see "Welcome to your course tour." And I log out And I log in as "student1" And I am on "Course 1" course homepage And I should see "Welcome to your course tour." And I click on "Got it" "button" And I log out And I log in as "teacher1" And I am on "Course 1" course homepage And I should see "Welcome to your course tour." @javascript Scenario: Add tour for a specific category and its subcategory Given the following "categories" exist: | name | category | idnumber | | MainCat | 0 | CAT1 | | SubCat | CAT1 | CAT2 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | CAT1 | | Course 2 | C2 | CAT2 | And the following "users" exist: | username | | student1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /course/view.php% | | Tour is enabled | 1 | | Category | MainCat | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your course tour. | Manual | And I log out And I log in as "student1" When I am on "Course 1" course homepage And I wait until the page is ready Then I should see "Welcome to your course tour." When I am on "Course 2" course homepage And I wait until the page is ready Then I should see "Welcome to your course tour." @javascript Scenario: Add tour for a specific courseformat Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | | Course 2 | C2 | weeks | And the following "users" exist: | username | | student1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /course/view.php% | | Tour is enabled | 1 | | Course format | Weekly format | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your course tour. | Manual | And I log out And I log in as "student1" When I am on "Course 1" course homepage And I wait until the page is ready Then I should not see "Welcome to your course tour." When I am on "Course 2" course homepage And I wait until the page is ready Then I should see "Welcome to your course tour." @javascript Scenario: Add tour for a specific course Given the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | | Course 2 | C2 | weeks | And the following "users" exist: | username | | student1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /course/view.php% | | Tour is enabled | 1 | | Courses | C1 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your course tour. | Manual | And I log out And I log in as "student1" When I am on "Course 1" course homepage And I wait until the page is ready Then I should see "Welcome to your course tour." When I am on "Course 2" course homepage And I wait until the page is ready Then I should not see "Welcome to your course tour." @javascript Scenario: Add tours with CSS selectors Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | Given the following "courses" exist: | fullname | shortname | format | enablecompletion | | Course 1 | C1 | topics | 1 | | Course 2 | C2 | topics | 1 | And the following "activities" exist: | activity | course | name | firstpagetitle | wikimode | | wiki | C1 | Test wiki name | First page | collaborative | And I log in as "admin" And I am on "Course 2" course homepage with editing mode on And I add a "Forum" to section "1" and I fill the form with: | Forum name | Test forum name | | Forum type | Standard forum for general use | | Description | Test forum description | And I add a new user tour with: | Name | Wiki tour | | Description | A tour with both matches | | Apply to URL match | /course/view.php% | | Tour is enabled | 1 | | CSS selector | .modtype_wiki | And I add steps to the "Wiki tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to the Wiki tour | Manual | And I add a new user tour with: | Name | Forum tour | | Description | A tour with both matches | | Apply to URL match | /course/view.php% | | Tour is enabled | 1 | | CSS selector | .modtype_forum | And I add steps to the "Forum tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to the Forum tour | Manual | And I am on "Course 1" course homepage Then I should see "Welcome to the Wiki tour" And I am on "Course 2" course homepage Then I should see "Welcome to the Forum tour" @javascript Scenario: Check filtering respects the sort order Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | The first tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | CSS selector | #page-my-index | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to the First tour | Manual | And I add a new user tour with: | Name | Second tour | | Description | The second tour | | Apply to URL match | /my/% | | Tour is enabled | 0 | | CSS selector | #page-my-index | And I add steps to the "Second tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to the Second tour | Manual | And I add a new user tour with: | Name | Third tour | | Description | The third tour | | Apply to URL match | /my/% | | Tour is enabled | 1 | | CSS selector | #page-my-index | And I add steps to the "Third tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to the Third tour | Manual | And I am on homepage Then I should see "Welcome to the First tour" And I open the User tour settings page And I click on "Move tour down" "link" in the "The first tour" "table_row" And I click on "Move tour down" "link" in the "The first tour" "table_row" And I am on homepage Then I should see "Welcome to the Third tour" tour_accessibility.feature 0000644 00000007217 15151252313 0012026 0 ustar 00 @tool @tool_usertours Feature: Apply accessibility to a tour Background: Given I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | FRONTPAGE | | Tour is enabled | 1 | | Show with backdrop | 1 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome tour. | Manual | And I add steps to the tour: | targettype | targetvalue_selector | Title | id_content | Content type | | Selector | .usermenu | User menu | Next page | Manual | | Selector | .navbar-brand | Page 2 | Next page | Manual | And I add steps to the tour: | targettype | Title | id_content | Content type | | Display in middle of page | Page 3 | Final page. | Manual | @javascript Scenario: Check tabbing working correctly. Given I am on site homepage And I wait "1" seconds And I should see "Welcome" # First dialogue of the tour, "Welcome". It has Next and End buttons. # Nothing highlighted on the page. Initially whole dialogue focused. When I press tab Then the focused element is "Next" "button" in the "Welcome" "dialogue" When I press tab Then the focused element is "Skip tour" "button" in the "Welcome" "dialogue" When I press tab # Here the focus loops round to the whole dialogue again. And I press tab Then the focused element is "Next" "button" in the "Welcome" "dialogue" # Check looping works properly going backwards too. When I press shift tab And I press shift tab Then the focused element is "Skip tour" "button" in the "Welcome" "dialogue" When I press "Next" # Now we are on the "User menu" step, so Previous is also enabled. # Also, the user menu section in the page is highlighted, and this # section contain a hyperlink so the focus have to go though and back to the dialogue. And I wait "1" seconds And I press tab Then the focused element is "Next" "button" in the "User menu" "dialogue" When I press tab Then the focused element is "Skip tour" "button" in the "User menu" "dialogue" # We tab 3 times from "Skip Tour" button to header container, drop down then go to "Dashboard" link. When I press tab Then the focused element is ".usermenu" "css_element" When I press tab Then the focused element is "User menu" "button" in the ".usermenu" "css_element" When I press tab And I press tab Then the focused element is "Next" "button" in the "User menu" "dialogue" # Press shift-tab twice should lead us back to the user menu button. When I press shift tab And I press shift tab Then the focused element is "User menu" "button" in the ".usermenu" "css_element" @javascript Scenario: Aria tags should not exist And I am on site homepage When I click on "Next" "button" And I click on "Next" "button" Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should exist And ".navbar-brand[tabindex]" "css_element" should exist When I click on "Next" "button" Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist And ".navbar-brand[tabindex]:not([tabindex='-1'])" "css_element" should not exist When I click on "End tour" "button" Then ".navbar-brand[aria-describedby^='tour-step-tool_usertours']" "css_element" should not exist And ".navbar-brand[tabindex]:not([tabindex='0'])" "css_element" should not exist reset_tour.feature 0000644 00000002077 15151252313 0010320 0 ustar 00 @tool @tool_usertours Feature: Reset a tour In order to test a tour As an administrator I can reset the tour to force it to display again Background: Given I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | FRONTPAGE | | Tour is enabled | 1 | | Show with backdrop | 1 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome tour. | Manual | @javascript Scenario: Reset the tour with mobile view # Changing the window viewport to mobile so we will have the footer section. Given I change viewport size to "480x800" And I am on site homepage And I should see "Welcome" And I press "Got it" And I should not see "Welcome" When I click on "Reset user tour on this page" "link" in the "#page-footer" "css_element" Then I should see "Welcome" duplicate_tour.feature 0000644 00000002410 15151252313 0011137 0 ustar 00 @tool @tool_usertours Feature: Duplicate a user tour As an administrator I want to duplicate a user tour @javascript Scenario: Tour can be duplicated Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I add a new user tour with: | Name | First tour | | Description | My first tour | | Apply to URL match | /my/% | | Tour is enabled | 0 | And I add steps to the "First tour" tour: | targettype | Title | id_content | Content type | | Display in middle of page | Welcome | Welcome to your personal learning space. We'd like to give you a quick tour to show you some of the areas you may find helpful | Manual | And I open the User tour settings page And I should see "1" occurrences of "First tour" in the "admintable" "table" And I click on "Duplicate" "link" in the "My first tour" "table_row" And I open the User tour settings page Then I should see "1" occurrences of "First tour (copy)" in the "admintable" "table" behat_tool_usertours.php 0000644 00000007143 15151252313 0011533 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * User tour related steps definitions. * * @package tool_usertours * @category test * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php'); use Behat\Gherkin\Node\TableNode as TableNode; /** * User tour related steps definitions. * * @package tool_usertours * @category test * @copyright 2016 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_tool_usertours extends behat_base { /** * Add a new user tour. * * @Given /^I add a new user tour with:$/ * @param TableNode $table */ public function i_add_a_new_user_tour_with(TableNode $table) { $this->execute('behat_tool_usertours::i_open_the_user_tour_settings_page'); $this->execute('behat_general::click_link', get_string('newtour', 'tool_usertours')); // Fill form and post. $this->execute('behat_forms::i_set_the_following_fields_to_these_values', $table); $this->execute('behat_forms::press_button', get_string('savechanges', 'moodle')); $this->execute('behat_general::i_wait_to_be_redirected'); } /** * Add new steps to a user tour. * * @Given /^I add steps to the "(?P<tour_name_string>(?:[^"]|\\")*)" tour:$/ * @param string $tourname The name of the tour to add steps to. * @param TableNode $table */ public function i_add_steps_to_the_named_tour($tourname, TableNode $table) { $this->execute('behat_tool_usertours::i_open_the_user_tour_settings_page'); $this->execute('behat_general::click_link', $this->escape($tourname)); $this->execute('behat_tool_usertours::i_add_steps_to_the_tour', $table); } /** * Add new steps to the current user tour. * * @Given /^I add steps to the tour:$/ * @param TableNode $table */ public function i_add_steps_to_the_tour(TableNode $table) { foreach ($table->getHash() as $step) { $this->execute('behat_general::click_link', get_string('newstep', 'tool_usertours')); foreach ($step as $locator => $value) { $this->execute('behat_forms::i_set_the_field_to', [$this->escape($locator), $this->escape($value)]); } $this->execute('behat_forms::press_button', get_string('savechanges', 'moodle')); $this->execute('behat_general::i_wait_to_be_redirected'); } } /** * Navigate to the user tour settings page. * * @Given /^I open the User tour settings page$/ */ public function i_open_the_user_tour_settings_page() { $this->execute('behat_navigation::i_navigate_to_in_site_administration', get_string('appearance', 'admin') . ' > ' . get_string('usertours', 'tool_usertours') ); } } block_recentlyaccesseditems_dashboard.feature 0000644 00000005771 15151252414 0015676 0 ustar 00 @block @block_recentlyaccesseditems @javascript Feature: The recently accessed items block allows users to easily access their most recently visited items In order to access the most recent items accessed As a user I can use the recently accessed items block in my dashboard Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | | Course 2 | C2 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student1 | C2 | student | And the following "activity" exists: | course | C1 | | activity | forum | | idnumber | Test forum name | | name | Test forum name | And I log in as "student1" Scenario: User has not accessed any item Then I should see "No recent items" in the "Recently accessed items" "block" Scenario: User has accessed some items Given I change window size to "large" When I am on the "Test forum name" "forum activity" page And I follow "Dashboard" Then I should see "Test forum name" in the "Recently accessed items" "block" And I should not see "Show more items" in the "Recently accessed items" "block" Scenario: User has accessed more than 3 items Given the following "activities" exist: | activity | name | intro | course | idnumber | | assign | Test assignment name | Test assignment description | C1 | assign1 | | book | Test book name | | C1 | book1 | | choice | Test choice name | Test choice description | C1 | choice1 | | data | Test database name | Test database description | C1 | data1 | And I change window size to "large" And I am on the "Test forum name" "forum activity" page And I am on the "Test database name" "data activity" page And I am on the "Test assignment name" "assign activity" page And I am on the "Test book name" "book activity" page And I am on the "Test choice name" "choice activity" page When I follow "Dashboard" Then I should see "Show more items" in the "Recently accessed items" "block" And I should not see "Test forum name" in the "Recently accessed items" "block" And I click on "Show more items" "button" in the "Recently accessed items" "block" And I should see "Test forum name" in the "Recently accessed items" "block" And I turn editing mode on And I configure the "Recently accessed items" block And I set the following fields to these values: | Region | content | And I press "Save changes" And I turn editing mode off And I should not see "Show more items" in the "Recently accessed items" "block" backup_user_data.feature 0000644 00000005610 15151261277 0011426 0 ustar 00 @tool @tool_recyclebin Feature: Backup user data As a teacher I want user data to be backed up when I delete a course module So that I can recover student content Background: Course with teacher and student exist. Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher@asd.com | | student1 | Student | 1 | student@asd.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following config values are set as admin: | coursebinenable | 1 | tool_recyclebin | | autohide | 0 | tool_recyclebin | And the following "activities" exist: | activity | course | section | name | intro | | quiz | C1 | 1 | Quiz 1 | Test quiz description | And the following "question categories" exist: | contextlevel | reference | name | | Course | C1 | Test questions | And the following "questions" exist: | questioncategory | qtype | name | questiontext | | Test questions | truefalse | TF1 | First question | | Test questions | truefalse | TF2 | Second question | And quiz "Quiz 1" contains the following questions: | question | page | | TF1 | 1 | | TF2 | 1 | @javascript Scenario Outline: Delete and restore a quiz with user data Given the following config values are set as admin: | restore_general_users | <include_user> | restore | And I am on the "Quiz 1" "quiz activity" page logged in as student1 And I press "Attempt quiz" And I click on "True" "radio" in the "First question" "question" And I click on "False" "radio" in the "Second question" "question" And I press "Finish attempt" And I press "Submit all and finish" And I click on "Submit" "button" in the "Submit all your answers and finish?" "dialogue" And I should see "50.00 out of 100.00" And I log out And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "Quiz 1" activity And I run all adhoc tasks And I navigate to "Recycle bin" in current page administration And I should see "Quiz 1" And I click on "Restore" "link" in the "region-main" "region" And I log out And I log in as "student1" And I am on "Course 1" course homepage When I navigate to "User report" in the course gradebook Then "Quiz 1" row "Grade" column of "user-grade" table should contain "50" And "Quiz 1" row "Percentage" column of "user-grade" table should contain "50" Examples: | include_user | case_explanation | | 1 | Checked | | 1 | Unchecked | basic_functionality.feature 0000644 00000014717 15151261277 0012173 0 ustar 00 @tool @tool_recyclebin Feature: Basic recycle bin functionality As a teacher I want be able to recover deleted content and manage the recycle bin content So that I can fix an accidental deletion and clean the recycle bin Background: Course with teacher exists. Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher@asd.com | | student1 | Student | 1 | student@asd.com | | student2 | Student | 2 | student2@asd.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | | Course 2 | C2 | And the following "activities" exist: | activity | course | section | name | intro | | assign | C1 | 1 | Test assign 1 | Test 1 | | assign | C1 | 1 | Test assign 2 | Test 2 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | student2 | C1 | student | | teacher1 | C2 | editingteacher | | student1 | C2 | student | | student2 | C2 | student | And the following "groups" exist: | name | course | idnumber | | Group A | C2 | G1 | | Group B | C2 | G2 | | Group C | C2 | G3 | And the following "group members" exist: | user | group | | teacher1 | G1 | | teacher1 | G2 | | student1 | G1 | | student2 | G1 | | student2 | G2 | And the following config values are set as admin: | coursebinenable | 1 | tool_recyclebin | | categorybinenable | 1 | tool_recyclebin | | coursebinexpiry | 604800 | tool_recyclebin | | categorybinexpiry | 1209600 | tool_recyclebin | | autohide | 0 | tool_recyclebin | Scenario: Restore a deleted assignment Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "Test assign 1" activity When I navigate to "Recycle bin" in current page administration Then I should see "Test assign 1" And I should see "Contents will be permanently deleted after 7 days" And I click on "Restore" "link" in the "region-main" "region" And I should see "'Test assign 1' has been restored" And I wait to be redirected And I am on "Course 1" course homepage And I should see "Test assign 1" in the "Topic 1" "section" Scenario: Restore a deleted course Given I log in as "admin" And I go to the courses management page And I click on "delete" action for "Course 2" in management course listing And I press "Delete" And I should see "Deleting C2" And I should see "C2 has been completely deleted" And I press "Continue" And I am on course index And I should see "Course 1" And I should not see "Course 2" When I navigate to "Recycle bin" in current page administration Then I should see "Course 2" And I should see "Contents will be permanently deleted after 14 days" And I click on "Restore" "link" in the "region-main" "region" And I should see "'Course 2' has been restored" And I wait to be redirected And I go to the courses management page And I should see "Course 2" in the "#course-listing" "css_element" And I am on the "Course 2" "groups" page And I select "Overview" from the "jump" singleselect And "Student 1" "text" should exist in the "Group A" "table_row" And "Student 2" "text" should exist in the "Group A" "table_row" And "Student 2" "text" should exist in the "Group B" "table_row" @javascript Scenario: Deleting a single item from the recycle bin Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "Test assign 1" activity And I run all adhoc tasks And I navigate to "Recycle bin" in current page administration When I click on "Delete" "link" Then I should see "Are you sure you want to delete the selected item from the recycle bin?" And I click on "Cancel" "button" in the "Confirmation" "dialogue" And I should see "Test assign 1" And I click on "Delete" "link" And I press "Yes" And I should see "'Test assign 1' has been deleted" And I should see "There are no items in the recycle bin." @javascript Scenario: Deleting all the items from the recycle bin Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "Test assign 1" activity And I delete "Test assign 2" activity And I run all adhoc tasks And I navigate to "Recycle bin" in current page administration And I should see "Test assign 1" And I should see "Test assign 2" When I click on "Delete all" "link" Then I should see "Are you sure you want to delete all items from the recycle bin?" And I click on "Cancel" "button" in the "Confirmation" "dialogue" And I should see "Test assign 1" And I should see "Test assign 2" And I click on "Delete all" "link" And I press "Yes" And I should see "Recycle bin has been emptied" And I should see "There are no items in the recycle bin." @javascript Scenario: Show recycle bin on category action menu Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I navigate to "Recycle bin" in current page administration Then I should see "There are no items in the recycle bin." @javascript Scenario: Not show recycle bin empty on category action menu whit autohide enable Given I log in as "admin" And the following config values are set as admin: | categorybinenable | 0 | tool_recyclebin | And I navigate to "Courses > Manage courses and categories" in site administration And I click on "Actions menu" "link" Then I should not see "Recycle bin" @javascript Scenario: Show recycle bin not empty on category action menu whit autohide enable Given I log in as "admin" And the following config values are set as admin: | autohide | 1 | tool_recyclebin | And I navigate to "Courses > Manage courses and categories" in site administration And I click on "Actions menu" "link" Then I should not see "Recycle bin" And I click on "delete" action for "Course 2" in management course listing And I press "Delete" And I should see "Deleting C2" And I should see "C2 has been completely deleted" And I press "Continue" When I click on "Actions menu" "link" Then I should see "Recycle bin" block_myprofile.feature 0000644 00000027334 15151264554 0011322 0 ustar 00 @block @block_myprofile Feature: The logged in user block allows users to view their profile information In order to enable the logged in user block As a user I can add the logged in user block and configure it to show my information Scenario: Configure the logged in user block to show / hide the users country Given the following "users" exist: | username | firstname | lastname | email | country | | teacher1 | Teacher | One | teacher1@example.com | AU | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display country | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Australia" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display country | Yes | And I press "Save changes" And I should see "Australia" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users city Given the following "users" exist: | username | firstname | lastname | email | city | | teacher1 | Teacher | One | teacher1@example.com | Perth | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display city | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Perth" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display city | Yes | And I press "Save changes" And I should see "Perth" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users email Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display email | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "teacher1@example.com" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display email | Yes | And I press "Save changes" And I should see "teacher1@example.com" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users phone Given the following "users" exist: | username | firstname | lastname | email | phone1 | | teacher1 | Teacher | One | teacher1@example.com | 555-5555 | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display phone | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "555-5555" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display phone | Yes | And I press "Save changes" And I should see "555-5555" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users mobile phone Given the following "users" exist: | username | firstname | lastname | email | phone2 | | teacher1 | Teacher | One | teacher1@example.com | 555-5555 | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display mobile phone | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "555-5555" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display mobile phone | Yes | And I press "Save changes" And I should see "555-5555" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users Institution Given the following "users" exist: | username | firstname | lastname | email | institution | | teacher1 | Teacher | One | teacher1@example.com | myinstitution | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display institution | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "myinstitution" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display institution | Yes | And I press "Save changes" And I should see "myinstitution" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users address Given the following "users" exist: | username | firstname | lastname | email | address | | teacher1 | Teacher | One | teacher1@example.com | myaddress | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display address | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "myaddress" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display address | Yes | And I press "Save changes" And I should see "myaddress" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users first access Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display first access | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "First access:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display first access | Yes | And I press "Save changes" And I should see "First access:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users last access Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display last access | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Last access:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display last access | Yes | And I press "Save changes" And I should see "Last access:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users current login Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display current login | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Log in:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display current login | Yes | And I press "Save changes" And I should see "Log in:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users last ip Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display last IP | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "IP:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display last IP | Yes | And I press "Save changes" And I should see "IP:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users idnumber Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | ID12345 | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display ID number | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "ID number:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display ID number | Yes | And I press "Save changes" And I should see "ID number:" in the "Logged in user" "block" Scenario: Configure the logged in user block to show / hide the users last login Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block And I configure the "Logged in user" block And I set the following fields to these values: | Display last login | No | And I press "Save changes" Then I should see "Teacher One" in the "Logged in user" "block" And I should not see "Last login:" in the "Logged in user" "block" And I configure the "Logged in user" block And I set the following fields to these values: | Display last login | Yes | And I press "Save changes" And I log out And I log in as "teacher1" And I should see "Last login:" in the "Logged in user" "block" block_myprofile_frontpage.feature 0000644 00000001740 15151264554 0013360 0 ustar 00 @block @block_myprofile Feature: The logged in user block allows users to view their profile information on the front page In order to enable the logged in user block on the frontpage As an admin I can add the logged in user block to the frontpage and view my information Background: Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | T1 | And I log in as "admin" And I am on site homepage And I turn editing mode on And I add the "Logged in user" block And I log out Scenario: Try to view the logged in user block as a guest Given I log in as "guest" When I am on site homepage Then I should not see "Logged in user" Scenario: View the logged in user block by a logged in user Given I log in as "teacher1" When I am on site homepage Then I should see "Teacher One" in the "Logged in user" "block" block_myprofile_course.feature 0000644 00000001672 15151264554 0012677 0 ustar 00 @block @block_myprofile Feature: The logged in user block allows users to view their profile information in a course In order to enable the logged in user block in a course As a teacher I can add the logged in user block to a course and view my information Scenario: View the logged in user block by a user in a course Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Logged in user" block Then I should see "Teacher One" in the "Logged in user" "block" block_myprofile_activity.feature 0000644 00000002226 15151264554 0013227 0 ustar 00 @block @block_myprofile Feature: The logged in user block allows users to view their profile information in an activity In order to enable the logged in user block in an activity As a teacher I can add the logged in user block to an activity and view my information Scenario: View the logged in user block by a user in an activity Given the following "users" exist: | username | firstname | lastname | email | idnumber | | teacher1 | Teacher | One | teacher1@example.com | T1 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | idnumber | name | intro | | page | C1 | page1 | Test page name | Test page description | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I follow "Test page name" When I add the "Logged in user" block Then I should see "Teacher One" in the "Logged in user" "block" block_myprofile_dashboard.feature 0000644 00000001263 15151264554 0013322 0 ustar 00 @block @block_myprofile Feature: The logged in user block allows users to view their profile information in on the dashboard In order to enable the logged in user block on the dashboard As a user I can add the logged in user block to a the dashboard and view my information Scenario: View the logged in user block by a user on the dashboard Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | One | teacher1@example.com | And I log in as "teacher1" And I turn editing mode on When I add the "Logged in user" block Then I should see "Teacher One" in the "Logged in user" "block" structural_changes.feature 0000644 00000025725 15151265112 0012033 0 ustar 00 @block @block_recent_activity Feature: View structural changes in recent activity block In order to know when activities were changed As a user In need to see the structural changes in recent activity block Background: Given the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Terry1 | Teacher1 | teacher1@example.com | | assistant1 | Terry2 | Teacher2 | teacher2@example.com | | student1 | Sam1 | Student1 | student1@example.com | | student2 | Sam2 | Student2 | student2@example.com | | student3 | Sam3 | Student3 | student3@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | assistant1 | C1 | teacher | | student1 | C1 | student | | student2 | C1 | student | | student3 | C1 | student | And the following "groups" exist: | name | course | idnumber | | Group 1 | C1 | G1 | | Group 2 | C1 | G2 | And the following "groupings" exist: | name | course | idnumber | | Grouping 1 | C1 | GG1 | | Grouping 2 | C1 | GG2 | | Grouping 3 | C1 | GG3 | And the following "group members" exist: | user | group | | student1 | G1 | | student2 | G2 | | student3 | G1 | | student3 | G2 | | assistant1 | G1 | And the following "grouping groups" exist: | grouping | group | | GG1 | G1 | | GG2 | G2 | | GG3 | G1 | | GG3 | G2 | Scenario: Check that Added module information is displayed respecting view capability Given the following "activities" exist: | activity | course | section | name | idnumber | description | groupmode | grouping | visible | | forum | C1 | 1 | ForumVisibleGroups | forum1 | No description | 2 | | 1 | | forum | C1 | 1 | ForumSeparateGroups | forum2 | No description | 1 | | 1 | | forum | C1 | 1 | ForumHidden | forum3 | No description | 1 | | 0 | | forum | C1 | 1 | ForumNoGroups | forum4 | No description | 0 | | 1 | | forum | C1 | 2 | ForumVisibleGroupsG1 | forum5 | No description | 2 | GG1 | 1 | | forum | C1 | 2 | ForumSeparateGroupsG1 | forum6 | No description | 1 | GG1 | 1 | | forum | C1 | 3 | ForumVisibleGroupsG2 | forum7 | No description | 2 | GG2 | 1 | | forum | C1 | 3 | ForumSeparateGroupsG2 | forum8 | No description | 1 | GG2 | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage And I click on "ForumVisibleGroupsG1" "link" And I click on "Settings" "link" And I set the following fields to these values: | Access restrictions | Grouping: Grouping 1 | And I press "Save and return to course" And I click on "ForumSeparateGroupsG1" "link" And I click on "Settings" "link" And I set the following fields to these values: | Access restrictions | Grouping: Grouping 1 | And I press "Save and return to course" And I click on "ForumVisibleGroupsG2" "link" And I click on "Settings" "link" And I set the following fields to these values: | Access restrictions | Grouping: Grouping 2 | And I press "Save and return to course" And I click on "ForumSeparateGroupsG2" "link" And I click on "Settings" "link" And I set the following fields to these values: | Access restrictions | Grouping: Grouping 2 | And I press "Save and return to course" And I am on "Course 1" course homepage with editing mode on When I add the "Recent activity" block Then I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should not see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should not see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I log out And I log in as "student2" And I am on "Course 1" course homepage And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should not see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should not see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I log out And I log in as "student3" And I am on "Course 1" course homepage And I should see "ForumVisibleGroups" in the "Recent activity" "block" And I should see "ForumSeparateGroups" in the "Recent activity" "block" And I should see "ForumNoGroups" in the "Recent activity" "block" And I should not see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I log out # Teachers have capability to see all groups and hidden activities And I log in as "assistant1" And I am on "Course 1" course homepage And I should see "ForumHidden" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG1" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG1" in the "Recent activity" "block" And I should see "ForumVisibleGroupsG2" in the "Recent activity" "block" And I should see "ForumSeparateGroupsG2" in the "Recent activity" "block" And I log out Scenario: Updates and deletes in recent activity block Given the following "activity" exists: | activity | forum | | course | C1 | | idnumber | forum1 | | name | ForumNew | | description | No description | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Recent activity" block Then I should see "Added Forum" in the "Recent activity" "block" And I should see "ForumNew" in the "Recent activity" "block" And I log out # Update forum as a teacher after a second to ensure we have a new timestamp for recent activity. And I wait "1" seconds And I log in as "student1" And I am on "Course 1" course homepage And I should see "Added Forum" in the "Recent activity" "block" And I should see "ForumNew" in the "Recent activity" "block" And I log out # Update forum as a teacher after a second to ensure we have a new timestamp for recent activity. And I wait "1" seconds # Update forum as a teacher And I log in as "teacher1" And I am on "Course 1" course homepage And I follow "ForumNew" And I navigate to "Settings" in current page administration And I set the following fields to these values: | name | ForumUpdated | And I press "Save and return to course" And I log out And I wait "1" seconds # Student 1 already saw that forum was created, now he can see that forum was updated And I log in as "student1" And I am on "Course 1" course homepage And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should see "Updated Forum" in the "Recent activity" "block" And I should see "ForumUpdated" in the "Recent activity" "block" And I log out And I wait "1" seconds # Student 2 has bigger interval and he can see one entry that forum was created but with the new name And I log in as "student2" And I am on "Course 1" course homepage And I should see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should see "ForumUpdated" in the "Recent activity" "block" And I log out And I wait "1" seconds # Delete forum as a teacher And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I delete "ForumUpdated" activity And I run all adhoc tasks And I log out And I wait "1" seconds # Students 1 and 2 see that forum was deleted And I log in as "student1" And I am on "Course 1" course homepage And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should not see "ForumUpdated" in the "Recent activity" "block" And I should see "Deleted Forum" in the "Recent activity" "block" And I log out And I wait "1" seconds # Student 3 never knew that forum was created, so he does not see anything And I log in as "student3" And I am on "Course 1" course homepage And I should not see "Added Forum" in the "Recent activity" "block" And I should not see "ForumNew" in the "Recent activity" "block" And I should not see "Updated Forum" in the "Recent activity" "block" And I should not see "ForumUpdated" in the "Recent activity" "block" And I should not see "Deleted Forum" in the "Recent activity" "block" role_renaming.feature 0000644 00000004203 15151776365 0010761 0 ustar 00 @core @core_course Feature: Rename roles in a course In order to account for course-level differences As a teacher I need to be able to rename roles Background: Given the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | Scenario: Teacher can rename roles Given I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I should see "Role renaming" When I set the following fields to these values: | Your word for 'Teacher' | Lecturer | | Your word for 'Student' | Learner | And I press "Save and display" And I navigate to course participants Then I should see "Lecturer (Teacher)" in the "Teacher 1" "table_row" And I should see "Learner (Student)" in the "Student 1" "table_row" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I navigate to course participants And I should see "Lecturer" in the "Teacher 1" "table_row" And I should see "Learner" in the "Student 1" "table_row" And I should not see "Lecturer (Teacher)" in the "Teacher 1" "table_row" And I should not see "Learner (Student)" in the "Student 1" "table_row" Scenario: Ability to rename roles can be prevented Given I log in as "admin" And I set the following system permissions of "Teacher" role: | capability | permission | | moodle/course:renameroles | Inherit | And I follow "Log out" When I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration Then I should not see "Role renaming" And I should not see "Your word for 'Teacher'" category_change_visibility.feature 0000644 00000041071 15151776366 0013536 0 ustar 00 @core @core_course Feature: We can change the visibility of categories in the management interface. As a moodle admin I need to test hiding and showing a category. I need to test hiding and showing a sub category. I need to test visibility is applied to sub categories. I need to test visibility is applied to courses. I need to test visibility of children is reset when changing back. # Tests hiding and then showing a single category. Scenario: Test making a category hidden and then visible again. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And I toggle visibility of category "CAT1" in management listing # Redirect. And I should see the "Course categories and courses" management page And category in management listing should be dimmed "CAT1" And I toggle visibility of category "CAT1" in management listing # Redirect. And I should see the "Course categories and courses" management page And category in management listing should be visible "CAT1" # Tests hiding and then showing a single category. @javascript Scenario: Test using AJAX to make a category hidden and then visible again. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be dimmed "CAT1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" # Tests hiding and then showing a subcategory. Scenario: Test making a subcategory hidden and then visible again. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | CAT1 | CAT2 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should not see "Cat 2" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And I click on category "Cat 1" in the management interface # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And I toggle visibility of category "CAT2" in management listing # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And category in management listing should be dimmed "CAT2" And I toggle visibility of category "CAT2" in management listing # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" # Tests hiding and then showing a subcategory. @javascript Scenario: Test using AJAX to make a subcategory hidden and then visible again. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | CAT1 | CAT2 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should not see "Cat 2" in the "#category-listing ul" "css_element" And category in management listing should be visible "CAT1" And I click to expand category "CAT1" in the management interface And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And I toggle visibility of category "CAT2" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be dimmed "CAT2" And I toggle visibility of category "CAT2" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" # The test below this is identical except with JavaScript enabled. Scenario: Test relation between category and course when changing visibility. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 3 | CAT1 | CAT3 | | Cat 4 | CAT1 | CAT4 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And I should see "Cat 4" in the "#category-listing ul" "css_element" And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" And I should see "Course 2" in the "#course-listing ul.course-list" "css_element" And I should see "Course 3" in the "#course-listing ul.course-list" "css_element" And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be visible "CAT3" And category in management listing should be visible "CAT4" And course in management listing should be visible "C1" And course in management listing should be visible "C2" And course in management listing should be visible "C3" And I toggle visibility of course "C2" in management listing # Redirect. And I should see the "Course categories and courses" management page with a course selected And course in management listing should be visible "C1" And course in management listing should be dimmed "C2" And course in management listing should be visible "C3" And I toggle visibility of category "CAT3" in management listing # Redirect. And I should see the "Course categories and courses" management page And I toggle visibility of category "CAT1" in management listing # Redirect. And I should see the "Course categories and courses" management page And category in management listing should be dimmed "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be dimmed "CAT3" And category in management listing should be dimmed "CAT4" And course in management listing should be dimmed "C1" And course in management listing should be dimmed "C2" And course in management listing should be dimmed "C3" And I toggle visibility of category "CAT1" in management listing # Redirect. And I should see the "Course categories and courses" management page And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be dimmed "CAT3" And category in management listing should be visible "CAT4" And course in management listing should be visible "C1" And course in management listing should be dimmed "C2" And course in management listing should be visible "C3" # The test above this is identical except without JavaScript enabled. @javascript @_cross_browser Scenario: Test the relation between category and course when changing visibility with AJAX Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 3 | CAT1 | CAT3 | | Cat 4 | CAT1 | CAT4 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And I should see "Cat 4" in the "#category-listing ul" "css_element" And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" And I should see "Course 2" in the "#course-listing ul.course-list" "css_element" And I should see "Course 3" in the "#course-listing ul.course-list" "css_element" And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be visible "CAT3" And category in management listing should be visible "CAT4" And course in management listing should be visible "C1" And course in management listing should be visible "C2" And course in management listing should be visible "C3" And I toggle visibility of course "C2" in management listing And a new page should not have loaded since I started watching And I should see "Cat 3" in the "#category-listing ul" "css_element" And course in management listing should be visible "C1" And course in management listing should be dimmed "C2" And course in management listing should be visible "C3" And I toggle visibility of category "CAT3" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be dimmed "CAT3" And category in management listing should be visible "CAT4" And course in management listing should be visible "C1" And course in management listing should be dimmed "C2" And course in management listing should be visible "C3" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be dimmed "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be dimmed "CAT3" And category in management listing should be dimmed "CAT4" And course in management listing should be dimmed "C1" And course in management listing should be dimmed "C2" And course in management listing should be dimmed "C3" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be dimmed "CAT3" And category in management listing should be visible "CAT4" And course in management listing should be visible "C1" And course in management listing should be dimmed "C2" And course in management listing should be visible "C3" @javascript @_cross_browser Scenario: Test courses are hidden when selected category parent is hidden. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | CAT1 | CAT2 | | Cat 3 | CAT2 | CAT3 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT3 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 2" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 3" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be visible "CAT3" And course in management listing should be visible "C1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be dimmed "CAT1" And category in management listing should be dimmed "CAT2" And category in management listing should be dimmed "CAT3" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be visible "CAT3" And course in management listing should be visible "C1" And I toggle visibility of course "C1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be visible "CAT3" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be dimmed "CAT1" And category in management listing should be dimmed "CAT2" And category in management listing should be dimmed "CAT3" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And category in management listing should be visible "CAT2" And category in management listing should be visible "CAT3" And course in management listing should be dimmed "C1" edit_settings.feature 0000644 00000005164 15151776366 0011015 0 ustar 00 @core @core_course Feature: Edit course settings In order to set the course according to my teaching needs As a teacher I need to edit the course settings @javascript Scenario: Edit course settings Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | summary | format | | Course 1 | C1 | <p>Course summary</p> | topics | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following config values are set as admin: | courselistshortnames | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage When I navigate to "Settings" in current page administration And I set the following fields to these values: | Course full name | Edited course fullname | | Course short name | Edited course shortname | | Course summary | Edited course summary | And I press "Save and display" And I am on site homepage Then I should not see "Course 1" And I should not see "C1" And I should see "Edited course fullname" And I should see "Edited course shortname" And I am on "Edited course fullname" course homepage And I navigate to "Settings" in current page administration And the field "Course full name" matches value "Edited course fullname" And the field "Course short name" matches value "Edited course shortname" And the field "Course summary" matches value "Edited course summary" And I am on site homepage And I should see "Edited course fullname" Scenario: Edit course settings and return to the management interface Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Categories" management page And I click on category "Cat 1" in the management interface And I should see the "Course categories and courses" management page When I click on "edit" action for "Course 1" in management course listing And I set the following fields to these values: | Course full name | Edited course fullname | | Course short name | Edited course shortname | | Course summary | Edited course summary | And I press "Save and return" Then I should see the "Course categories and courses" management page behat_course.php 0000644 00000255063 15151776366 0007754 0 ustar 00 <?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Behat course-related steps definitions. * * @package core_course * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. require_once(__DIR__ . '/../../../lib/behat/behat_base.php'); use Behat\Gherkin\Node\TableNode as TableNode, Behat\Mink\Exception\ExpectationException as ExpectationException, Behat\Mink\Exception\DriverException as DriverException, Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; /** * Course-related steps definitions. * * @package core_course * @category test * @copyright 2012 David Monllaó * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_course extends behat_base { /** * Return the list of partial named selectors. * * @return array */ public static function get_partial_named_selectors(): array { return [ new behat_component_named_selector( 'Activity chooser screen', [ "%core_course/activityChooser%//*[@data-region=%locator%][contains(concat(' ', @class, ' '), ' carousel-item ')]" ] ), new behat_component_named_selector( 'Activity chooser tab', [ "%core_course/activityChooser%//*[@data-region=%locator%][contains(concat(' ', @class, ' '), ' tab-pane ')]" ] ), ]; } /** * Return a list of the Mink named replacements for the component. * * Named replacements allow you to define parts of an xpath that can be reused multiple times, or in multiple * xpaths. * * This method should return a list of {@link behat_component_named_replacement} and the docs on that class explain * how it works. * * @return behat_component_named_replacement[] */ public static function get_named_replacements(): array { return [ new behat_component_named_replacement( 'activityChooser', ".//*[contains(concat(' ', @class, ' '), ' modchooser ')][contains(concat(' ', @class, ' '), ' modal-dialog ')]" ), ]; } /** * Creates a new course with the provided table data matching course settings names with the desired values. * * @Given /^I create a course with:$/ * @param TableNode $table The course data */ public function i_create_a_course_with(TableNode $table) { // Go to course management page. $this->i_go_to_the_courses_management_page(); // Ensure you are on course management page. $this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcourses')); // Select default course category. $this->i_click_on_category_in_the_management_interface(get_string('defaultcategoryname')); $this->execute("behat_course::i_should_see_the_courses_management_page", get_string('categoriesandcourses')); // Click create new course. $this->execute('behat_general::i_click_on_in_the', array(get_string('createnewcourse'), "link", "#course-listing", "css_element") ); // If the course format is one of the fields we change how we // fill the form as we need to wait for the form to be set. $rowshash = $table->getRowsHash(); $formatfieldrefs = array(get_string('format'), 'format', 'id_format'); foreach ($formatfieldrefs as $fieldref) { if (!empty($rowshash[$fieldref])) { $formatfield = $fieldref; } } // Setting the format separately. if (!empty($formatfield)) { // Removing the format field from the TableNode. $rows = $table->getRows(); $formatvalue = $rowshash[$formatfield]; foreach ($rows as $key => $row) { if ($row[0] == $formatfield) { unset($rows[$key]); } } $table = new TableNode($rows); // Adding a forced wait until editors are loaded as otherwise selenium sometimes tries clicks on the // format field when the editor is being rendered and the click misses the field coordinates. $this->execute("behat_forms::i_expand_all_fieldsets"); $this->execute("behat_forms::i_set_the_field_to", array($formatfield, $formatvalue)); } // Set form fields. $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $table); // Save course settings. $this->execute("behat_forms::press_button", get_string('savechangesanddisplay')); } /** * Goes to the system courses/categories management page. * * @Given /^I go to the courses management page$/ */ public function i_go_to_the_courses_management_page() { $parentnodes = get_string('courses', 'admin'); // Go to home page. $this->execute("behat_general::i_am_on_homepage"); // Navigate to course management via system administration. $this->execute("behat_navigation::i_navigate_to_in_site_administration", array($parentnodes . ' > ' . get_string('coursemgmt', 'admin')) ); } /** * Adds the selected activity/resource filling the form data with the specified field/value pairs. Sections 0 and 1 are also allowed on frontpage. * * @When /^I add a "(?P<activity_or_resource_name_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)" and I fill the form with:$/ * @param string $activity The activity name * @param int $section The section number * @param TableNode $data The activity field/value data */ public function i_add_to_section_and_i_fill_the_form_with($activity, $section, TableNode $data) { // Add activity to section. $this->execute("behat_course::i_add_to_section", array($this->escape($activity), $this->escape($section)) ); // Wait to be redirected. $this->execute('behat_general::wait_until_the_page_is_ready'); // Set form fields. $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data); // Save course settings. $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse')); } /** * Opens the activity chooser and opens the activity/resource form page. Sections 0 and 1 are also allowed on frontpage. * * @Given /^I add a "(?P<activity_or_resource_name_string>(?:[^"]|\\")*)" to section "(?P<section_number>\d+)"$/ * @throws ElementNotFoundException Thrown by behat_base::find * @param string $activity * @param int $section */ public function i_add_to_section($activity, $section) { $this->require_javascript('Please use the \'the following "activity" exists:\' data generator instead.'); if ($this->getSession()->getPage()->find('css', 'body#page-site-index') && (int) $section <= 1) { // We are on the frontpage. if ($section) { // Section 1 represents the contents on the frontpage. $sectionxpath = "//body[@id='page-site-index']" . "/descendant::div[contains(concat(' ',normalize-space(@class),' '),' sitetopic ')]"; } else { // Section 0 represents "Site main menu" block. $sectionxpath = "//*[contains(concat(' ',normalize-space(@class),' '),' block_site_main_menu ')]"; } } else { // We are inside the course. $sectionxpath = "//li[@id='section-" . $section . "']"; } // Clicks add activity or resource section link. $sectionnode = $this->find('xpath', $sectionxpath); $this->execute('behat_general::i_click_on_in_the', [ get_string('addresourceoractivity', 'moodle'), 'button', $sectionnode, 'NodeElement', ]); // Clicks the selected activity if it exists. $activityliteral = behat_context_helper::escape(ucfirst($activity)); $activityxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' modchooser ')]" . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optioninfo ')]" . "/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' optionname ')]" . "[normalize-space(.)=$activityliteral]" . "/parent::a"; $this->execute('behat_general::i_click_on', [$activityxpath, 'xpath']); } /** * Opens a section edit menu if it is not already opened. * * @Given /^I open section "(?P<section_number>\d+)" edit menu$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $sectionnumber */ public function i_open_section_edit_menu($sectionnumber) { if (!$this->running_javascript()) { throw new DriverException('Section edit menu not available when Javascript is disabled'); } // Wait for section to be available, before clicking on the menu. $this->i_wait_until_section_is_available($sectionnumber); // If it is already opened we do nothing. $xpath = $this->section_exists($sectionnumber); $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@data-toggle, 'dropdown')]"; $exception = new ExpectationException('Section "' . $sectionnumber . '" was not found', $this->getSession()); $menu = $this->find('xpath', $xpath, $exception); $menu->click(); $this->i_wait_until_section_is_available($sectionnumber); } /** * Deletes course section. * * @Given /^I delete section "(?P<section_number>\d+)"$/ * @param int $sectionnumber The section number */ public function i_delete_section($sectionnumber) { // Ensures the section exists. $xpath = $this->section_exists($sectionnumber); // We need to know the course format as the text strings depends on them. $courseformat = $this->get_course_format(); if (get_string_manager()->string_exists('deletesection', $courseformat)) { $strdelete = get_string('deletesection', $courseformat); } else { $strdelete = get_string('deletesection'); } // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // Click on delete link. $this->execute('behat_general::i_click_on_in_the', array($strdelete, "link", $this->escape($xpath), "xpath_element") ); } /** * Turns course section highlighting on. * * @Given /^I turn section "(?P<section_number>\d+)" highlighting on$/ * @param int $sectionnumber The section number */ public function i_turn_section_highlighting_on($sectionnumber) { // Ensures the section exists. $xpath = $this->section_exists($sectionnumber); // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // Click on highlight topic link. $this->execute('behat_general::i_click_on_in_the', array(get_string('highlight'), "link", $this->escape($xpath), "xpath_element") ); } /** * Turns course section highlighting off. * * @Given /^I turn section "(?P<section_number>\d+)" highlighting off$/ * @param int $sectionnumber The section number */ public function i_turn_section_highlighting_off($sectionnumber) { // Ensures the section exists. $xpath = $this->section_exists($sectionnumber); // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // Click on un-highlight topic link. $this->execute('behat_general::i_click_on_in_the', array(get_string('highlightoff'), "link", $this->escape($xpath), "xpath_element") ); } /** * Shows the specified hidden section. You need to be in the course page and on editing mode. * * @Given /^I show section "(?P<section_number>\d+)"$/ * @param int $sectionnumber */ public function i_show_section($sectionnumber) { $showlink = $this->show_section_link_exists($sectionnumber); // Ensure section edit menu is open before interacting with it. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } $showlink->click(); if ($this->running_javascript()) { $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS); $this->i_wait_until_section_is_available($sectionnumber); } } /** * Hides the specified visible section. You need to be in the course page and on editing mode. * * @Given /^I hide section "(?P<section_number>\d+)"$/ * @param int $sectionnumber */ public function i_hide_section($sectionnumber) { // Ensures the section exists. $xpath = $this->section_exists($sectionnumber); // We need to know the course format as the text strings depends on them. $courseformat = $this->get_course_format(); if (get_string_manager()->string_exists('hidefromothers', $courseformat)) { $strhide = get_string('hidefromothers', $courseformat); } else { $strhide = get_string('hidesection'); } // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // Click on delete link. $this->execute('behat_general::i_click_on_in_the', array($strhide, "link", $this->escape($xpath), "xpath_element") ); if ($this->running_javascript()) { $this->getSession()->wait(self::get_timeout() * 1000, self::PAGE_READY_JS); $this->i_wait_until_section_is_available($sectionnumber); } } /** * Go to editing section page for specified section number. You need to be in the course page and on editing mode. * * @Given /^I edit the section "(?P<section_number>\d+)"$/ * @param int $sectionnumber */ public function i_edit_the_section($sectionnumber) { // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // We need to know the course format as the text strings depends on them. $courseformat = $this->get_course_format(); if ($sectionnumber > 0 && get_string_manager()->string_exists('editsection', $courseformat)) { $stredit = get_string('editsection', $courseformat); } else { $stredit = get_string('editsection'); } // Click on un-highlight topic link. $this->execute('behat_general::i_click_on_in_the', array($stredit, "link", "#section-" . $sectionnumber . " .action-menu", "css_element") ); } /** * Edit specified section and fill the form data with the specified field/value pairs. * * @When /^I edit the section "(?P<section_number>\d+)" and I fill the form with:$/ * @param int $sectionnumber The section number * @param TableNode $data The activity field/value data */ public function i_edit_the_section_and_i_fill_the_form_with($sectionnumber, TableNode $data) { // Edit given section. $this->execute("behat_course::i_edit_the_section", $sectionnumber); // Set form fields. $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data); // Save section settings. $this->execute("behat_forms::press_button", get_string('savechanges')); } /** * Checks if the specified course section hightlighting is turned on. You need to be in the course page on editing mode. * * @Then /^section "(?P<section_number>\d+)" should be highlighted$/ * @throws ExpectationException * @param int $sectionnumber The section number */ public function section_should_be_highlighted($sectionnumber) { // Ensures the section exists. $xpath = $this->section_exists($sectionnumber); $this->execute('behat_general::should_exist_in_the', ['Highlighted', 'text', $xpath, 'xpath_element']); // The important checking, we can not check the img. $this->execute('behat_general::should_exist_in_the', ['Remove highlight', 'link', $xpath, 'xpath_element']); } /** * Checks if the specified course section highlighting is turned off. You need to be in the course page on editing mode. * * @Then /^section "(?P<section_number>\d+)" should not be highlighted$/ * @throws ExpectationException * @param int $sectionnumber The section number */ public function section_should_not_be_highlighted($sectionnumber) { // We only catch ExpectationException, ElementNotFoundException should be thrown if the specified section does not exist. try { $this->section_should_be_highlighted($sectionnumber); } catch (ExpectationException $e) { // ExpectedException means that it is not highlighted. return; } throw new ExpectationException('The "' . $sectionnumber . '" section is highlighted', $this->getSession()); } /** * 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. * * @Then /^section "(?P<section_number>\d+)" should be hidden$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param int $sectionnumber */ public function section_should_be_hidden($sectionnumber) { $sectionxpath = $this->section_exists($sectionnumber); // Preventive in case there is any action in progress. // Adding it here because we are interacting (click) with // the elements, not necessary when we just find(). $this->i_wait_until_section_is_available($sectionnumber); // Section should be hidden. $exception = new ExpectationException('The section is not hidden', $this->getSession()); $this->find('xpath', $sectionxpath . "[contains(concat(' ', normalize-space(@class), ' '), ' hidden ')]", $exception); } /** * 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. * * @Then /^all activities in section "(?P<section_number>\d+)" should be hidden$/ * @throws ExpectationException * @throws ElementNotFoundException Thrown by behat_base::find * @param int $sectionnumber */ public function section_activities_should_be_hidden($sectionnumber) { $sectionxpath = $this->section_exists($sectionnumber); // Preventive in case there is any action in progress. // Adding it here because we are interacting (click) with // the elements, not necessary when we just find(). $this->i_wait_until_section_is_available($sectionnumber); // The checking are different depending on user permissions. if ($this->is_course_editor()) { // The section must be hidden. $this->show_section_link_exists($sectionnumber); // If there are activities they should be hidden and the visibility icon should not be available. if ($activities = $this->get_section_activities($sectionxpath)) { $dimmedexception = new ExpectationException('There are activities that are not hidden', $this->getSession()); foreach ($activities as $activity) { // Hidden from students. $this->find('named_partial', array('badge', get_string('hiddenfromstudents')), $dimmedexception, $activity); } } } else { // There shouldn't be activities. if ($this->get_section_activities($sectionxpath)) { throw new ExpectationException('There are activities in the section and they should be hidden', $this->getSession()); } } } /** * 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. * * @Then /^section "(?P<section_number>\d+)" should be visible$/ * @throws ExpectationException * @param int $sectionnumber */ public function section_should_be_visible($sectionnumber) { $sectionxpath = $this->section_exists($sectionnumber); // Section should not be hidden. $xpath = $sectionxpath . "[not(contains(concat(' ', normalize-space(@class), ' '), ' hidden '))]"; if (!$this->getSession()->getPage()->find('xpath', $xpath)) { throw new ExpectationException('The section is hidden', $this->getSession()); } // Edit menu should be visible. if ($this->is_course_editor()) { $xpath = $sectionxpath . "/descendant::div[contains(@class, 'section-actions')]" . "/descendant::a[contains(@data-toggle, 'dropdown')]"; if (!$this->getSession()->getPage()->find('xpath', $xpath)) { throw new ExpectationException('The section edit menu is not available', $this->getSession()); } } } /** * Moves up the specified section, this step only works with Javascript disabled. Editing mode should be on. * * @Given /^I move up section "(?P<section_number>\d+)"$/ * @throws DriverException Step not available when Javascript is enabled * @param int $sectionnumber */ public function i_move_up_section($sectionnumber) { if ($this->running_javascript()) { throw new DriverException('Move a section up step is not available with Javascript enabled'); } // Ensures the section exists. $sectionxpath = $this->section_exists($sectionnumber); // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // Follows the link $moveuplink = $this->get_node_in_container('link', get_string('moveup'), 'xpath_element', $sectionxpath); $moveuplink->click(); } /** * Moves down the specified section, this step only works with Javascript disabled. Editing mode should be on. * * @Given /^I move down section "(?P<section_number>\d+)"$/ * @throws DriverException Step not available when Javascript is enabled * @param int $sectionnumber */ public function i_move_down_section($sectionnumber) { if ($this->running_javascript()) { throw new DriverException('Move a section down step is not available with Javascript enabled'); } // Ensures the section exists. $sectionxpath = $this->section_exists($sectionnumber); // If javascript is on, link is inside a menu. if ($this->running_javascript()) { $this->i_open_section_edit_menu($sectionnumber); } // Follows the link $movedownlink = $this->get_node_in_container('link', get_string('movedown'), 'xpath_element', $sectionxpath); $movedownlink->click(); } /** * 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. * * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be visible$/ * @param string $activityname * @throws ExpectationException */ public function activity_should_be_visible($activityname) { // The activity must exists and be visible. $activitynode = $this->get_activity_node($activityname); if ($this->is_course_editor()) { // The activity should not be hidden from students. try { $this->find('named_partial', array('badge', get_string('hiddenfromstudents')), null, $activitynode); throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession()); } catch (ElementNotFoundException $e) { // All ok. } // Additional check if this is a teacher in editing mode. if ($this->is_editing_on()) { // The 'Hide' button should be available. $nohideexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' . get_string('hide') . '" icon', $this->getSession()); $this->find('named_partial', array('link', get_string('hide')), $nohideexception, $activitynode); } } } /** * 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. * * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be available but hidden from course page$/ * @param string $activityname * @throws ExpectationException */ public function activity_should_be_available_but_hidden_from_course_page($activityname) { if ($this->is_course_editor()) { // The activity must exists and be visible. $activitynode = $this->get_activity_node($activityname); // Should not have the "Hidden from students" badge. try { $this->find('named_partial', array('badge', get_string('hiddenfromstudents')), null, $activitynode); throw new ExpectationException('"' . $activityname . '" is hidden', $this->getSession()); } catch (ElementNotFoundException $e) { // All ok. } // Should have the "Available but not shown on course page" badge. $exception = new ExpectationException('"' . $activityname . '" is not Available', $this->getSession()); $this->find('named_partial', array('badge', get_string('hiddenoncoursepage')), $exception, $activitynode); // Additional check if this is a teacher in editing mode. if ($this->is_editing_on()) { // Also has either 'Hide' or 'Make unavailable' edit control. $nohideexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('hide') . '" nor "' . get_string('makeunavailable') . '" icons', $this->getSession()); try { $this->find('named_partial', array('link', get_string('hide')), false, $activitynode); } catch (ElementNotFoundException $e) { $this->find('named_partial', array('link', get_string('makeunavailable')), $nohideexception, $activitynode); } } } else { // Student should not see the activity at all. try { $this->get_activity_node($activityname); throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession()); } catch (ElementNotFoundException $e) { // This is good, the activity should not be there. } } } /** * 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. * * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" activity should be hidden$/ * @param string $activityname * @throws ExpectationException */ public function activity_should_be_hidden($activityname) { if ($this->is_course_editor()) { // The activity should exist. $activitynode = $this->get_activity_node($activityname); // Should be hidden. $exception = new ExpectationException('"' . $activityname . '" is not hidden', $this->getSession()); $this->find('named_partial', array('badge', get_string('hiddenfromstudents')), $exception, $activitynode); // Additional check if this is a teacher in editing mode. if ($this->is_editing_on()) { // Also has either 'Show' or 'Make available' edit control. $noshowexception = new ExpectationException('"' . $activityname . '" has neither "' . get_string('show') . '" nor "' . get_string('makeavailable') . '" icons', $this->getSession()); try { $this->find('named_partial', array('link', get_string('show')), false, $activitynode); } catch (ElementNotFoundException $e) { $this->find('named_partial', array('link', get_string('makeavailable')), $noshowexception, $activitynode); } } } else { // It should not exist at all. try { $this->get_activity_node($activityname); throw new ExpectationException('The "' . $activityname . '" should not appear', $this->getSession()); } catch (ElementNotFoundException $e) { // This is good, the activity should not be there. } } } /** * Checks that the specified label is hidden from students. You need to be in the course page. * * @Then /^"(?P<activity_or_resource_string>(?:[^"]|\\")*)" label should be hidden$/ * @param string $activityname * @throws ExpectationException */ public function label_should_be_hidden($activityname) { if ($this->is_course_editor()) { // The activity should exist. $activitynode = $this->get_activity_node($activityname); // Should be hidden. $exception = new ExpectationException('"' . $activityname . '" is not hidden', $this->getSession()); $this->find('named_partial', array('badge', get_string('hiddenfromstudents')), $exception, $activitynode); } } /** * Moves the specified activity to the first slot of a section. * * Editing mode should be on. * * @Given /^I move "(?P<activity_name_string>(?:[^"]|\\")*)" activity to section "(?P<section_number>\d+)"$/ * @param string $activityname The activity name * @param int $sectionnumber The number of section */ public function i_move_activity_to_section($activityname, $sectionnumber): void { // Ensure the destination is valid. $sectionxpath = $this->section_exists($sectionnumber); // Not all formats are compatible with the move tool. $activitynode = $this->get_activity_node($activityname); if (!$activitynode->find('css', "[data-action='moveCm']", false, false, 0)) { // Execute the legacy YUI move option. $this->i_move_activity_to_section_yui($activityname, $sectionnumber); return; } // JS enabled. if ($this->running_javascript()) { $this->i_open_actions_menu($activityname); $this->execute( 'behat_course::i_click_on_in_the_activity', [get_string('move'), "link", $this->escape($activityname)] ); $this->execute("behat_general::i_click_on_in_the", [ "[data-for='section'][data-number='$sectionnumber']", 'css_element', "[data-region='modal-container']", 'css_element' ]); } else { $this->execute( 'behat_course::i_click_on_in_the_activity', [get_string('move'), "link", $this->escape($activityname)] ); $this->execute( 'behat_general::i_click_on_in_the', ["li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element"] ); } } /** * Moves the specified activity to the first slot of a section using the YUI course format. * * This step is experimental when using it in Javascript tests. Editing mode should be on. * * @param string $activityname The activity name * @param int $sectionnumber The number of section */ public function i_move_activity_to_section_yui($activityname, $sectionnumber): void { // Ensure the destination is valid. $sectionxpath = $this->section_exists($sectionnumber); // JS enabled. if ($this->running_javascript()) { $activitynode = $this->get_activity_element('Move', 'icon', $activityname); $destinationxpath = $sectionxpath . "/descendant::ul[contains(concat(' ', normalize-space(@class), ' '), ' yui3-dd-drop ')]"; $this->execute( "behat_general::i_drag_and_i_drop_it_in", [ $this->escape($activitynode->getXpath()), "xpath_element", $this->escape($destinationxpath), "xpath_element", ] ); } else { // Following links with no-JS. // Moving to the fist spot of the section (before all other section's activities). $this->execute( 'behat_course::i_click_on_in_the_activity', ["a.editing_move", "css_element", $this->escape($activityname)] ); $this->execute( 'behat_general::i_click_on_in_the', ["li.movehere a", "css_element", $this->escape($sectionxpath), "xpath_element"] ); } } /** * Edits the activity name through the edit activity; this step only works with Javascript enabled. Editing mode should be on. * * @Given /^I change "(?P<activity_name_string>(?:[^"]|\\")*)" activity name to "(?P<new_name_string>(?:[^"]|\\")*)"$/ * @throws DriverException Step not available when Javascript is disabled * @param string $activityname * @param string $newactivityname */ public function i_change_activity_name_to($activityname, $newactivityname) { $this->execute('behat_forms::i_set_the_field_in_container_to', [ get_string('edittitle'), $activityname, 'activity', $newactivityname ]); } /** * Opens an activity actions menu if it is not already opened. * * @Given /^I open "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname */ public function i_open_actions_menu($activityname) { if (!$this->running_javascript()) { throw new DriverException('Activities actions menu not available when Javascript is disabled'); } // If it is already opened we do nothing. $activitynode = $this->get_activity_node($activityname); // Find the menu. $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]'); if (!$menunode) { throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname), $this->getSession()); } $expanded = $menunode->getAttribute('aria-expanded'); if ($expanded == 'true') { return; } $this->execute('behat_course::i_click_on_in_the_activity', array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname)) ); $this->actions_menu_should_be_open($activityname); } /** * Closes an activity actions menu if it is not already closed. * * @Given /^I close "(?P<activity_name_string>(?:[^"]|\\")*)" actions menu$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname */ public function i_close_actions_menu($activityname) { if (!$this->running_javascript()) { throw new DriverException('Activities actions menu not available when Javascript is disabled'); } // If it is already closed we do nothing. $activitynode = $this->get_activity_node($activityname); // Find the menu. $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]'); if (!$menunode) { throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname), $this->getSession()); } $expanded = $menunode->getAttribute('aria-expanded'); if ($expanded != 'true') { return; } $this->execute('behat_course::i_click_on_in_the_activity', array("a[data-toggle='dropdown']", "css_element", $this->escape($activityname)) ); } /** * Checks that the specified activity's action menu is open. * * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should be open$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname */ public function actions_menu_should_be_open($activityname) { if (!$this->running_javascript()) { throw new DriverException('Activities actions menu not available when Javascript is disabled'); } $activitynode = $this->get_activity_node($activityname); // Find the menu. $menunode = $activitynode->find('css', 'a[data-toggle=dropdown]'); if (!$menunode) { throw new ExpectationException(sprintf('Could not find actions menu for the activity "%s"', $activityname), $this->getSession()); } $expanded = $menunode->getAttribute('aria-expanded'); if ($expanded != 'true') { throw new ExpectationException(sprintf("The action menu for '%s' is not open", $activityname), $this->getSession()); } } /** * Checks that the specified activity's action menu contains an item. * * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname * @param string $menuitem */ public function actions_menu_should_have_item($activityname, $menuitem) { $activitynode = $this->get_activity_node($activityname); $notfoundexception = new ExpectationException('"' . $activityname . '" doesn\'t have a "' . $menuitem . '" item', $this->getSession()); $this->find('named_partial', array('link', $menuitem), $notfoundexception, $activitynode); } /** * Checks that the specified activity's action menu does not contains an item. * * @Then /^"(?P<activity_name_string>(?:[^"]|\\")*)" actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/ * @throws DriverException The step is not available when Javascript is disabled * @param string $activityname * @param string $menuitem */ public function actions_menu_should_not_have_item($activityname, $menuitem) { $activitynode = $this->get_activity_node($activityname); try { $this->find('named_partial', array('link', $menuitem), false, $activitynode); throw new ExpectationException('"' . $activityname . '" has a "' . $menuitem . '" item when it should not', $this->getSession()); } catch (ElementNotFoundException $e) { // This is good, the menu item should not be there. } } /** * Indents to the right the activity or resource specified by it's name. Editing mode should be on. * * @Given /^I indent right "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ * @param string $activityname */ public function i_indent_right_activity($activityname) { $activity = $this->escape($activityname); if ($this->running_javascript()) { $this->i_open_actions_menu($activity); } $this->execute('behat_course::i_click_on_in_the_activity', array(get_string('moveright'), "link", $this->escape($activity)) ); } /** * Indents to the left the activity or resource specified by it's name. Editing mode should be on. * * @Given /^I indent left "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ * @param string $activityname */ public function i_indent_left_activity($activityname) { $activity = $this->escape($activityname); if ($this->running_javascript()) { $this->i_open_actions_menu($activity); } $this->execute('behat_course::i_click_on_in_the_activity', array(get_string('moveleft'), "link", $this->escape($activity)) ); } /** * 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. * * @Given /^I delete "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ * @param string $activityname */ public function i_delete_activity($activityname) { $steps = array(); $activity = $this->escape($activityname); if ($this->running_javascript()) { $this->i_open_actions_menu($activity); } $this->execute('behat_course::i_click_on_in_the_activity', array(get_string('delete'), "link", $this->escape($activity)) ); // JS enabled. // Not using chain steps here because the exceptions catcher have problems detecting // JS modal windows and avoiding interacting them at the same time. if ($this->running_javascript()) { $this->execute('behat_general::i_click_on_in_the', array(get_string('yes'), "button", "Confirm", "dialogue") ); } else { $this->execute("behat_forms::press_button", get_string('yes')); } return $steps; } /** * Duplicates the activity or resource specified by it's name. You should be in the course page with editing mode on. * * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ * @param string $activityname */ public function i_duplicate_activity($activityname) { $steps = array(); $activity = $this->escape($activityname); if ($this->running_javascript()) { $this->i_open_actions_menu($activity); } $this->execute('behat_course::i_click_on_in_the_activity', array(get_string('duplicate'), "link", $activity) ); } /** * 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. * * @Given /^I duplicate "(?P<activity_name_string>(?:[^"]|\\")*)" activity editing the new copy with:$/ * @param string $activityname * @param TableNode $data */ public function i_duplicate_activity_editing_the_new_copy_with($activityname, TableNode $data) { $activity = $this->escape($activityname); $activityliteral = behat_context_helper::escape($activityname); $this->execute("behat_course::i_duplicate_activity", $activity); // Determine the future new activity xpath from the former one. $duplicatedxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" . "[contains(., $activityliteral)]/following-sibling::li"; $duplicatedactionsmenuxpath = $duplicatedxpath . "/descendant::a[@data-toggle='dropdown']"; if ($this->running_javascript()) { // We wait until the AJAX request finishes and the section is visible again. $hiddenlightboxxpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]" . "[contains(., $activityliteral)]" . "/ancestor::li[contains(concat(' ', normalize-space(@class), ' '), ' section ')]" . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]"; $this->execute("behat_general::wait_until_exists", array($this->escape($hiddenlightboxxpath), "xpath_element") ); // Close the original activity actions menu. $this->i_close_actions_menu($activity); // The next sibling of the former activity will be the duplicated one, so we click on it from it's xpath as, at // this point, it don't even exists in the DOM (the steps are executed when we return them). $this->execute('behat_general::i_click_on', array($this->escape($duplicatedactionsmenuxpath), "xpath_element") ); } // We force the xpath as otherwise mink tries to interact with the former one. $this->execute('behat_general::i_click_on_in_the', array(get_string('editsettings'), "link", $this->escape($duplicatedxpath), "xpath_element") ); $this->execute("behat_forms::i_set_the_following_fields_to_these_values", $data); $this->execute("behat_forms::press_button", get_string('savechangesandreturntocourse')); } /** * 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. * * Using the protected method as this method will be usually * called by other methods which are not returning a set of * steps and performs the actions directly, so it would not * be executed if it returns another step. * * Hopefully we would not require test writers to use this step * and we will manage it from other step definitions. * * @Given /^I wait until section "(?P<section_number>\d+)" is available$/ * @param int $sectionnumber * @return void */ public function i_wait_until_section_is_available($sectionnumber) { // Looks for a hidden lightbox or a non-existent lightbox in that section. $sectionxpath = $this->section_exists($sectionnumber); $hiddenlightboxxpath = $sectionxpath . "/descendant::div[contains(concat(' ', @class, ' '), ' lightbox ')][contains(@style, 'display: none')]" . " | " . $sectionxpath . "[count(child::div[contains(@class, 'lightbox')]) = 0]"; $this->ensure_element_exists($hiddenlightboxxpath, 'xpath_element'); } /** * Clicks on the specified element of the activity. You should be in the course page with editing mode turned on. * * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<activity_name_string>(?:[^"]|\\")*)" activity$/ * @param string $element * @param string $selectortype * @param string $activityname */ public function i_click_on_in_the_activity($element, $selectortype, $activityname) { $element = $this->get_activity_element($element, $selectortype, $activityname); $element->click(); } /** * Clicks on the specified element inside the activity container. * * @throws ElementNotFoundException * @param string $element * @param string $selectortype * @param string $activityname * @return NodeElement */ protected function get_activity_element($element, $selectortype, $activityname) { $activitynode = $this->get_activity_node($activityname); $exception = new ElementNotFoundException($this->getSession(), "'{$element}' '{$selectortype}' in '${activityname}'"); return $this->find($selectortype, $element, $exception, $activitynode); } /** * Checks if the course section exists. * * @throws ElementNotFoundException Thrown by behat_base::find * @param int $sectionnumber * @return string The xpath of the section. */ protected function section_exists($sectionnumber) { // Just to give more info in case it does not exist. $xpath = "//li[@id='section-" . $sectionnumber . "']"; $exception = new ElementNotFoundException($this->getSession(), "Section $sectionnumber "); $this->find('xpath', $xpath, $exception); return $xpath; } /** * Returns the show section icon or throws an exception. * * @throws ElementNotFoundException Thrown by behat_base::find * @param int $sectionnumber * @return NodeElement */ protected function show_section_link_exists($sectionnumber) { // Gets the section xpath and ensure it exists. $xpath = $this->section_exists($sectionnumber); // We need to know the course format as the text strings depends on them. $courseformat = $this->get_course_format(); // Checking the show button alt text and show icon. $showtext = get_string('showfromothers', $courseformat); $linkxpath = $xpath . "//a[*[contains(text(), " . behat_context_helper::escape($showtext) . ")]]"; $exception = new ElementNotFoundException($this->getSession(), 'Show section link'); // Returing the link so both Non-JS and JS browsers can interact with it. return $this->find('xpath', $linkxpath, $exception); } /** * Returns the hide section icon link if it exists or throws exception. * * @throws ElementNotFoundException Thrown by behat_base::find * @param int $sectionnumber * @return NodeElement */ protected function hide_section_link_exists($sectionnumber) { // Gets the section xpath and ensure it exists. $xpath = $this->section_exists($sectionnumber); // We need to know the course format as the text strings depends on them. $courseformat = $this->get_course_format(); // Checking the hide button alt text and hide icon. $hidetext = behat_context_helper::escape(get_string('hidefromothers', $courseformat)); $linkxpath = $xpath . "/descendant::a[@title=$hidetext]"; $exception = new ElementNotFoundException($this->getSession(), 'Hide section icon '); $this->find('icon', 'Hide', $exception); // Returing the link so both Non-JS and JS browsers can interact with it. return $this->find('xpath', $linkxpath, $exception); } /** * Gets the current course format. * * @throws ExpectationException If we are not in the course view page. * @return string The course format in a frankenstyled name. */ protected function get_course_format() { $exception = new ExpectationException('You are not in a course page', $this->getSession()); // The moodle body's id attribute contains the course format. $node = $this->getSession()->getPage()->find('css', 'body'); if (!$node) { throw $exception; } if (!$bodyid = $node->getAttribute('id')) { throw $exception; } if (strstr($bodyid, 'page-course-view-') === false) { throw $exception; } return 'format_' . str_replace('page-course-view-', '', $bodyid); } /** * Gets the section's activites DOM nodes. * * @param string $sectionxpath * @return array NodeElement instances */ protected function get_section_activities($sectionxpath) { $xpath = $sectionxpath . "/descendant::li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')]"; // We spin here, as activities usually require a lot of time to load. try { $activities = $this->find_all('xpath', $xpath); } catch (ElementNotFoundException $e) { return false; } return $activities; } /** * Returns the DOM node of the activity from <li>. * * @throws ElementNotFoundException Thrown by behat_base::find * @param string $activityname The activity name * @return NodeElement */ protected function get_activity_node($activityname) { $activityname = behat_context_helper::escape($activityname); $xpath = "//li[contains(concat(' ', normalize-space(@class), ' '), ' activity ')][contains(., $activityname)]"; return $this->find('xpath', $xpath); } /** * Gets the activity instance name from the activity node. * * @throws ElementNotFoundException * @param NodeElement $activitynode * @return string */ protected function get_activity_name($activitynode) { $instancenamenode = $this->find('xpath', "//span[contains(concat(' ', normalize-space(@class), ' '), ' instancename ')]", false, $activitynode); return $instancenamenode->getText(); } /** * Returns whether the user can edit the course contents or not. * * @return bool */ protected function is_course_editor(): bool { try { $this->find('field', get_string('editmode'), false, false, 0); return true; } catch (ElementNotFoundException $e) { return false; } } /** * Returns whether the user can edit the course contents and the editing mode is on. * * @return bool */ protected function is_editing_on() { $body = $this->find('xpath', "//body", false, false, 0); return $body->hasClass('editing'); } /** * Returns the category node from within the listing on the management page. * * @param string $idnumber * @return \Behat\Mink\Element\NodeElement */ protected function get_management_category_listing_node_by_idnumber($idnumber) { $id = $this->get_category_id($idnumber); $selector = sprintf('#category-listing .listitem-category[data-id="%d"] > div', $id); return $this->find('css', $selector); } /** * Returns a category node from within the management interface. * * @param string $name The name of the category. * @param bool $link If set to true we'll resolve to the link rather than just the node. * @return \Behat\Mink\Element\NodeElement */ protected function get_management_category_listing_node_by_name($name, $link = false) { $selector = "//div[@id='category-listing']//li[contains(concat(' ', normalize-space(@class), ' '), ' listitem-category ')]//a[text()='{$name}']"; if ($link === false) { $selector .= "/ancestor::li[@data-id][1]"; } return $this->find('xpath', $selector); } /** * Returns a course node from within the management interface. * * @param string $name The name of the course. * @param bool $link If set to true we'll resolve to the link rather than just the node. * @return \Behat\Mink\Element\NodeElement */ protected function get_management_course_listing_node_by_name($name, $link = false) { $selector = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$name}']"; if ($link === false) { $selector .= "/ancestor::li[@data-id]"; } return $this->find('xpath', $selector); } /** * Returns the course node from within the listing on the management page. * * @param string $idnumber * @return \Behat\Mink\Element\NodeElement */ protected function get_management_course_listing_node_by_idnumber($idnumber) { $id = $this->get_course_id($idnumber); $selector = sprintf('#course-listing .listitem-course[data-id="%d"] > div', $id); return $this->find('css', $selector); } /** * Clicks on a category in the management interface. * * @Given /^I click on category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $name */ public function i_click_on_category_in_the_management_interface($name) { $node = $this->get_management_category_listing_node_by_name($name, true); $node->click(); } /** * Clicks on a course in the management interface. * * @Given /^I click on course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $name */ public function i_click_on_course_in_the_management_interface($name) { $node = $this->get_management_course_listing_node_by_name($name, true); $node->click(); } /** * Clicks on a category checkbox in the management interface, if not checked. * * @Given /^I select category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $name */ public function i_select_category_in_the_management_interface($name) { $node = $this->get_management_category_listing_node_by_name($name); $node = $node->findField('bcat[]'); if (!$node->isChecked()) { $node->click(); } } /** * Clicks on a category checkbox in the management interface, if checked. * * @Given /^I unselect category "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $name */ public function i_unselect_category_in_the_management_interface($name) { $node = $this->get_management_category_listing_node_by_name($name); $node = $node->findField('bcat[]'); if ($node->isChecked()) { $node->click(); } } /** * Clicks course checkbox in the management interface, if not checked. * * @Given /^I select course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $name */ public function i_select_course_in_the_management_interface($name) { $node = $this->get_management_course_listing_node_by_name($name); $node = $node->findField('bc[]'); if (!$node->isChecked()) { $node->click(); } } /** * Clicks course checkbox in the management interface, if checked. * * @Given /^I unselect course "(?P<name_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $name */ public function i_unselect_course_in_the_management_interface($name) { $node = $this->get_management_course_listing_node_by_name($name); $node = $node->findField('bc[]'); if ($node->isChecked()) { $node->click(); } } /** * Move selected categories to top level in the management interface. * * @Given /^I move category "(?P<name_string>(?:[^"]|\\")*)" to top level in the management interface$/ * @param string $name */ public function i_move_category_to_top_level_in_the_management_interface($name) { $this->i_select_category_in_the_management_interface($name); $this->execute('behat_forms::i_set_the_field_to', array('menumovecategoriesto', core_course_category::get(0)->get_formatted_name()) ); // Save event. $this->execute("behat_forms::press_button", "bulkmovecategories"); } /** * Checks that a category is a subcategory of specific category. * * @Given /^I should see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/ * @throws ExpectationException * @param string $subcatidnumber * @param string $catidnumber */ public function i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) { $categorynodeid = $this->get_category_id($catidnumber); $subcategoryid = $this->get_category_id($subcatidnumber); $exception = new ExpectationException('The category '.$subcatidnumber.' is not a subcategory of '.$catidnumber, $this->getSession()); $selector = sprintf('#category-listing .listitem-category[data-id="%d"] .listitem-category[data-id="%d"]', $categorynodeid, $subcategoryid); $this->find('css', $selector, $exception); } /** * Checks that a category is not a subcategory of specific category. * * @Given /^I should not see category "(?P<subcatidnumber_string>(?:[^"]|\\")*)" as subcategory of "(?P<catidnumber_string>(?:[^"]|\\")*)" in the management interface$/ * @throws ExpectationException * @param string $subcatidnumber * @param string $catidnumber */ public function i_should_not_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber) { try { $this->i_should_see_category_as_subcategory_of_in_the_management_interface($subcatidnumber, $catidnumber); } catch (ExpectationException $e) { // ExpectedException means that it is not highlighted. return; } throw new ExpectationException('The category '.$subcatidnumber.' is a subcategory of '.$catidnumber, $this->getSession()); } /** * Click to expand a category revealing its sub categories within the management UI. * * @Given /^I click to expand category "(?P<idnumber_string>(?:[^"]|\\")*)" in the management interface$/ * @param string $idnumber */ public function i_click_to_expand_category_in_the_management_interface($idnumber) { $categorynode = $this->get_management_category_listing_node_by_idnumber($idnumber); $exception = new ExpectationException('Category "' . $idnumber . '" does not contain an expand or collapse toggle.', $this->getSession()); $togglenode = $this->find('css', 'a[data-action=collapse],a[data-action=expand]', $exception, $categorynode); $togglenode->click(); } /** * Checks that a category within the management interface is visible. * * @Given /^category in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/ * @param string $idnumber */ public function category_in_management_listing_should_be_visible($idnumber) { $id = $this->get_category_id($idnumber); $exception = new ExpectationException('The category '.$idnumber.' is not visible.', $this->getSession()); $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="1"]', $id); $this->find('css', $selector, $exception); } /** * Checks that a category within the management interface is dimmed. * * @Given /^category in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/ * @param string $idnumber */ public function category_in_management_listing_should_be_dimmed($idnumber) { $id = $this->get_category_id($idnumber); $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible="0"]', $id); $exception = new ExpectationException('The category '.$idnumber.' is visible.', $this->getSession()); $this->find('css', $selector, $exception); } /** * Checks that a course within the management interface is visible. * * @Given /^course in management listing should be visible "(?P<idnumber_string>(?:[^"]|\\")*)"$/ * @param string $idnumber */ public function course_in_management_listing_should_be_visible($idnumber) { $id = $this->get_course_id($idnumber); $exception = new ExpectationException('The course '.$idnumber.' is not visible.', $this->getSession()); $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="1"]', $id); $this->find('css', $selector, $exception); } /** * Checks that a course within the management interface is dimmed. * * @Given /^course in management listing should be dimmed "(?P<idnumber_string>(?:[^"]|\\")*)"$/ * @param string $idnumber */ public function course_in_management_listing_should_be_dimmed($idnumber) { $id = $this->get_course_id($idnumber); $exception = new ExpectationException('The course '.$idnumber.' is visible.', $this->getSession()); $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible="0"]', $id); $this->find('css', $selector, $exception); } /** * Toggles the visibility of a course in the management UI. * * If it was visible it will be hidden. If it is hidden it will be made visible. * * @Given /^I toggle visibility of course "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/ * @param string $idnumber */ public function i_toggle_visibility_of_course_in_management_listing($idnumber) { $id = $this->get_course_id($idnumber); $selector = sprintf('#course-listing .listitem-course[data-id="%d"][data-visible]', $id); $node = $this->find('css', $selector); $exception = new ExpectationException('Course listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession()); if ($node->getAttribute('data-visible') === '1') { $toggle = $this->find('css', '.action-hide', $exception, $node); } else { $toggle = $this->find('css', '.action-show', $exception, $node); } $toggle->click(); } /** * Toggles the visibility of a category in the management UI. * * If it was visible it will be hidden. If it is hidden it will be made visible. * * @Given /^I toggle visibility of category "(?P<idnumber_string>(?:[^"]|\\")*)" in management listing$/ */ public function i_toggle_visibility_of_category_in_management_listing($idnumber) { $id = $this->get_category_id($idnumber); $selector = sprintf('#category-listing .listitem-category[data-id="%d"][data-visible]', $id); $node = $this->find('css', $selector); $exception = new ExpectationException('Category listing "' . $idnumber . '" does not contain a show or hide toggle.', $this->getSession()); if ($node->getAttribute('data-visible') === '1') { $toggle = $this->find('css', '.action-hide', $exception, $node); } else { $toggle = $this->find('css', '.action-show', $exception, $node); } $toggle->click(); } /** * Moves a category displayed in the management interface up or down one place. * * @Given /^I click to move category "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/ * * @param string $idnumber The category idnumber * @param string $direction The direction to move in, either up or down */ public function i_click_to_move_category_by_one($idnumber, $direction) { $node = $this->get_management_category_listing_node_by_idnumber($idnumber); $this->user_moves_listing_by_one('category', $node, $direction); } /** * Moves a course displayed in the management interface up or down one place. * * @Given /^I click to move course "(?P<idnumber_string>(?:[^"]|\\")*)" (?P<direction>up|down) one$/ * * @param string $idnumber The course idnumber * @param string $direction The direction to move in, either up or down */ public function i_click_to_move_course_by_one($idnumber, $direction) { $node = $this->get_management_course_listing_node_by_idnumber($idnumber); $this->user_moves_listing_by_one('course', $node, $direction); } /** * Moves a course or category listing within the management interface up or down by one. * * @param string $listingtype One of course or category * @param \Behat\Mink\Element\NodeElement $listingnode * @param string $direction One of up or down. * @param bool $highlight If set to false we don't check the node has been highlighted. */ protected function user_moves_listing_by_one($listingtype, $listingnode, $direction, $highlight = true) { $up = (strtolower($direction) === 'up'); if ($up) { $exception = new ExpectationException($listingtype.' listing does not contain a moveup button.', $this->getSession()); $button = $this->find('css', 'a.action-moveup', $exception, $listingnode); } else { $exception = new ExpectationException($listingtype.' listing does not contain a movedown button.', $this->getSession()); $button = $this->find('css', 'a.action-movedown', $exception, $listingnode); } $button->click(); if ($this->running_javascript() && $highlight) { $listitem = $listingnode->getParent(); $exception = new ExpectationException('Nothing was highlighted, ajax didn\'t occur or didn\'t succeed.', $this->getSession()); $this->spin(array($this, 'listing_is_highlighted'), $listitem->getTagName().'#'.$listitem->getAttribute('id'), 2, $exception, true); } } /** * Used by spin to determine the callback has been highlighted. * * @param behat_course $self A self reference (default first arg from a spin callback) * @param \Behat\Mink\Element\NodeElement $selector * @return bool */ protected function listing_is_highlighted($self, $selector) { $listitem = $this->find('css', $selector); return $listitem->hasClass('highlight'); } /** * Check that one course appears before another in the course category management listings. * * @Given /^I should see course listing "(?P<preceedingcourse_string>(?:[^"]|\\")*)" before "(?P<followingcourse_string>(?:[^"]|\\")*)"$/ * * @param string $preceedingcourse The first course to find * @param string $followingcourse The second course to find (should be AFTER the first course) * @throws ExpectationException */ public function i_should_see_course_listing_before($preceedingcourse, $followingcourse) { $xpath = "//div[@id='course-listing']//li[contains(concat(' ', @class, ' '), ' listitem-course ')]//a[text()='{$preceedingcourse}']/ancestor::li[@data-id]//following::a[text()='{$followingcourse}']"; $msg = "{$preceedingcourse} course does not appear before {$followingcourse} course"; if (!$this->getSession()->getDriver()->find($xpath)) { throw new ExpectationException($msg, $this->getSession()); } } /** * Check that one category appears before another in the course category management listings. * * @Given /^I should see category listing "(?P<preceedingcategory_string>(?:[^"]|\\")*)" before "(?P<followingcategory_string>(?:[^"]|\\")*)"$/ * * @param string $preceedingcategory The first category to find * @param string $followingcategory The second category to find (should be after the first category) * @throws ExpectationException */ public function i_should_see_category_listing_before($preceedingcategory, $followingcategory) { $xpath = "//div[@id='category-listing']//li[contains(concat(' ', @class, ' '), ' listitem-category ')]//a[text()='{$preceedingcategory}']/ancestor::li[@data-id]//following::a[text()='{$followingcategory}']"; $msg = "{$preceedingcategory} category does not appear before {$followingcategory} category"; if (!$this->getSession()->getDriver()->find($xpath)) { throw new ExpectationException($msg, $this->getSession()); } } /** * Checks that we are on the course management page that we expect to be on and that no course has been selected. * * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page$/ * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses' */ public function i_should_see_the_courses_management_page($mode) { switch ($mode) { case "Courses": $heading = "Manage courses"; $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element")); $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); break; case "Course categories": $heading = "Manage course categories"; $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); $this->execute("behat_general::should_not_exist", array("#course-listing", "css_element")); break; case "Courses categories and courses": default: $heading = "Manage course categories and courses"; $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); break; } $this->execute("behat_general::assert_element_contains_text", array($heading, "h2", "css_element") ); $this->execute("behat_general::should_not_exist", array("#course-detail", "css_element")); } /** * Checks that we are on the course management page that we expect to be on and that a course has been selected. * * @Given /^I should see the "(?P<mode_string>(?:[^"]|\\")*)" management page with a course selected$/ * @param string $mode The mode to expected. One of 'Courses', 'Course categories' or 'Course categories and courses' */ public function i_should_see_the_courses_management_page_with_a_course_selected($mode) { switch ($mode) { case "Courses": $heading = "Manage courses"; $this->execute("behat_general::should_not_exist", array("#category-listing", "css_element")); $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); break; case "Course categories": $heading = "Manage course categories"; $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); break; case "Courses categories and courses": default: $heading = "Manage course categories and courses"; $this->execute("behat_general::should_exist", array("#category-listing", "css_element")); $this->execute("behat_general::should_exist", array("#course-listing", "css_element")); break; } $this->execute("behat_general::assert_element_contains_text", array($heading, "h2", "css_element")); $this->execute("behat_general::should_exist", array("#course-detail", "css_element")); } /** * Locates a course in the course category management interface and then triggers an action for it. * * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management course listing$/ * * @param string $action The action to take. One of * @param string $name The name of the course as it is displayed in the management interface. */ public function i_click_on_action_for_item_in_management_course_listing($action, $name) { $node = $this->get_management_course_listing_node_by_name($name); $this->user_clicks_on_management_listing_action('course', $node, $action); } /** * Locates a category in the course category management interface and then triggers an action for it. * * @Given /^I click on "(?P<action_string>(?:[^"]|\\")*)" action for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/ * * @param string $action The action to take. One of * @param string $name The name of the category as it is displayed in the management interface. */ public function i_click_on_action_for_item_in_management_category_listing($action, $name) { $node = $this->get_management_category_listing_node_by_name($name); $this->user_clicks_on_management_listing_action('category', $node, $action); } /** * Clicks to expand or collapse a category displayed on the frontpage * * @Given /^I toggle "(?P<categoryname_string>(?:[^"]|\\")*)" category children visibility in frontpage$/ * @throws ExpectationException * @param string $categoryname */ public function i_toggle_category_children_visibility_in_frontpage($categoryname) { $headingtags = array(); for ($i = 1; $i <= 6; $i++) { $headingtags[] = 'self::h' . $i; } $exception = new ExpectationException('"' . $categoryname . '" category can not be found', $this->getSession()); $categoryliteral = behat_context_helper::escape($categoryname); $xpath = "//div[@class='info']/descendant::*[" . implode(' or ', $headingtags) . "][contains(@class,'categoryname')][./descendant::a[.=$categoryliteral]]"; $node = $this->find('xpath', $xpath, $exception); $node->click(); // Smooth expansion. $this->getSession()->wait(1000); } /** * Finds the node to use for a management listitem action and clicks it. * * @param string $listingtype Either course or category. * @param \Behat\Mink\Element\NodeElement $listingnode * @param string $action The action being taken * @throws Behat\Mink\Exception\ExpectationException */ protected function user_clicks_on_management_listing_action($listingtype, $listingnode, $action) { $actionsnode = $listingnode->find('xpath', "//*" . "[contains(concat(' ', normalize-space(@class), ' '), '{$listingtype}-item-actions')]"); if (!$actionsnode) { throw new ExpectationException("Could not find the actions for $listingtype", $this->getSession()); } $actionnode = $actionsnode->find('css', '.action-'.$action); if (!$actionnode) { throw new ExpectationException("Expected action was not available or not found ($action)", $this->getSession()); } if ($this->running_javascript() && !$actionnode->isVisible()) { $actionsnode->find('css', 'a[data-toggle=dropdown]')->click(); $actionnode = $actionsnode->find('css', '.action-'.$action); } $actionnode->click(); } /** * Clicks on a category in the management interface. * * @Given /^I click on "(?P<categoryname_string>(?:[^"]|\\")*)" category in the management category listing$/ * @param string $name The name of the category to click. */ public function i_click_on_category_in_the_management_category_listing($name) { $node = $this->get_management_category_listing_node_by_name($name); $node->find('css', 'a.categoryname')->click(); } /** * Locates a category in the course category management interface and then opens action menu for it. * * @Given /^I open the action menu for "(?P<name_string>(?:[^"]|\\")*)" in management category listing$/ * * @param string $name The name of the category as it is displayed in the management interface. */ public function i_open_the_action_menu_for_item_in_management_category_listing($name) { $node = $this->get_management_category_listing_node_by_name($name); $node->find('xpath', "//*[contains(@class, 'category-item-actions')]//a[@data-toggle='dropdown']")->click(); } /** * Checks that the specified category actions menu contains an item. * * @Then /^"(?P<name_string>(?:[^"]|\\")*)" category actions menu should have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/ * * @param string $name * @param string $menuitem * @throws Behat\Mink\Exception\ExpectationException */ public function category_actions_menu_should_have_item($name, $menuitem) { $node = $this->get_management_category_listing_node_by_name($name); $notfoundexception = new ExpectationException('"' . $name . '" doesn\'t have a "' . $menuitem . '" item', $this->getSession()); $this->find('named_partial', ['link', $menuitem], $notfoundexception, $node); } /** * Checks that the specified category actions menu does not contain an item. * * @Then /^"(?P<name_string>(?:[^"]|\\")*)" category actions menu should not have "(?P<menu_item_string>(?:[^"]|\\")*)" item$/ * * @param string $name * @param string $menuitem * @throws Behat\Mink\Exception\ExpectationException */ public function category_actions_menu_should_not_have_item($name, $menuitem) { $node = $this->get_management_category_listing_node_by_name($name); try { $this->find('named_partial', ['link', $menuitem], false, $node); throw new ExpectationException('"' . $name . '" has a "' . $menuitem . '" item when it should not', $this->getSession()); } catch (ElementNotFoundException $e) { // This is good, the menu item should not be there. } } /** * Go to the course participants * * @Given /^I navigate to course participants$/ */ public function i_navigate_to_course_participants() { $this->execute('behat_navigation::i_select_from_secondary_navigation', get_string('participants')); } /** * Check that one teacher appears before another in the course contacts. * * @Given /^I should see teacher "(?P<pteacher_string>(?:[^"]|\\")*)" before "(?P<fteacher_string>(?:[^"]|\\")*)" in the course contact listing$/ * * @param string $pteacher The first teacher to find * @param string $fteacher The second teacher to find (should be after the first teacher) * * @throws ExpectationException */ public function i_should_see_teacher_before($pteacher, $fteacher) { $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']"; $msg = "Teacher {$pteacher} does not appear before Teacher {$fteacher}"; if (!$this->getSession()->getDriver()->find($xpath)) { throw new ExpectationException($msg, $this->getSession()); } } /** * Check that one teacher oes not appears after another in the course contacts. * * @Given /^I should not see teacher "(?P<fteacher_string>(?:[^"]|\\")*)" after "(?P<pteacher_string>(?:[^"]|\\")*)" in the course contact listing$/ * * @param string $fteacher The teacher that should not be found (after the other teacher) * @param string $pteacher The teacher after who the other should not be found (this teacher must be found!) * * @throws ExpectationException */ public function i_should_not_see_teacher_after($fteacher, $pteacher) { $xpathliteral = behat_context_helper::escape($pteacher); $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; try { $nodes = $this->find_all('xpath', $xpath); } catch (ElementNotFoundException $e) { throw new ExpectationException('"' . $pteacher . '" text was not found in the page', $this->getSession()); } $xpath = "//ul[contains(@class,'teachers')]//li//a[text()='{$pteacher}']/ancestor::li//following::a[text()='{$fteacher}']"; $msg = "Teacher {$fteacher} appears after Teacher {$pteacher}"; if ($this->getSession()->getDriver()->find($xpath)) { throw new ExpectationException($msg, $this->getSession()); } } /** * Open the activity chooser in a course. * * @Given /^I open the activity chooser$/ */ public function i_open_the_activity_chooser() { $this->execute('behat_general::i_click_on', array('//button[@data-action="open-chooser"]', 'xpath_element')); $node = $this->get_selected_node('xpath_element', '//div[@data-region="modules"]'); $this->ensure_node_is_visible($node); } /** * Checks the presence of the given text in the activity's displayed dates. * * @Given /^the activity date in "(?P<activityname>(?:[^"]|\\")*)" should contain "(?P<text>(?:[^"]|\\")*)"$/ * @param string $activityname The activity name. * @param string $text The text to be searched in the activity date. */ public function activity_date_in_activity_should_contain_text(string $activityname, string $text): void { $containerselector = "//div[@data-activityname='$activityname']"; $containerselector .= "//div[@data-region='activity-dates']"; $params = [$text, $containerselector, 'xpath_element']; $this->execute("behat_general::assert_element_contains_text", $params); } /** * Checks the presence of activity dates information in the activity information output component. * * @Given /^the activity date information in "(?P<activityname>(?:[^"]|\\")*)" should exist$/ * @param string $activityname The activity name. */ public function activity_dates_information_in_activity_should_exist(string $activityname): void { $containerselector = "//div[@data-activityname='$activityname']"; $elementselector = "//div[@data-region='activity-dates']"; $params = [$elementselector, "xpath_element", $containerselector, "xpath_element"]; $this->execute("behat_general::should_exist_in_the", $params); } /** * Checks the absence of activity dates information in the activity information output component. * * @Given /^the activity date information in "(?P<activityname>(?:[^"]|\\")*)" should not exist$/ * @param string $activityname The activity name. */ public function activity_dates_information_in_activity_should_not_exist(string $activityname): void { $containerselector = "//div[@data-region='activity-information'][@data-activityname='$activityname']"; try { $this->find('xpath_element', $containerselector); } catch (ElementNotFoundException $e) { // If activity information container does not exist (activity dates not shown, completion info not shown), all good. return; } // Otherwise, ensure that the completion information does not exist. $elementselector = "//div[@data-region='activity-dates']"; $params = [$elementselector, "xpath_element", $containerselector, "xpath_element"]; $this->execute("behat_general::should_not_exist_in_the", $params); } } paged_course_navigation.feature 0000644 00000013764 15151776366 0013034 0 ustar 00 @core @core_course Feature: Course paged mode In order to split the course in parts As a teacher I need to display the course in a paged mode and navigate through the different sections @javascript @_cross_browser Scenario Outline: Weekly and topics course formats with Javascript enabled Given the following "courses" exist: | fullname | shortname | category | format | coursedisplay | numsections | startdate | | Course 1 | C1 | 0 | <courseformat> | 1 | 3 | 0 | And I log in as "admin" And I am on "Course 1" course homepage Then I click on <section2> "link" in the <section2> "section" And I am on "Course 1" course homepage And I click on <section3> "link" in the <section3> "section" And I am on "Course 1" course homepage And I click on <section1> "link" in the <section1> "section" And I should see <section1> in the "div.single-section" "css_element" And I should see <section2> in the ".single-section div.nextsection" "css_element" And I should not see <prevunexistingsection> in the ".single-section" "css_element" And I click on <section2> "link" in the ".single-section" "css_element" And I should see <section2> in the "div.single-section" "css_element" And I should see <section1> in the ".single-section div.prevsection" "css_element" And I should see <section3> in the ".single-section div.nextsection" "css_element" And I click on <section1> "link" in the ".single-section" "css_element" And I should see <section1> in the "div.single-section" "css_element" And I click on <section2> "link" in the ".single-section" "css_element" And I click on <section3> "link" in the ".single-section" "css_element" And I should see <section3> in the "div.single-section" "css_element" And I should see <section2> in the ".single-section div.prevsection" "css_element" And I should not see <section1> in the ".single-section .section-navigation" "css_element" And I should not see <prevunexistingsection> in the ".single-section" "css_element" And I should not see <nextunexistingsection> in the ".single-section" "css_element" Examples: | courseformat | section1 | section2 | section3 | prevunexistingsection | nextunexistingsection | | topics | "Topic 1" | "Topic 2" | "Topic 3" | "Topic 0" | "Topic 4" | | weeks | "1 January - 7 January" | "8 January - 14 January" | "15 January - 21 January" | "25 December - 31 December" | "22 January - 28 January" | @javascript Scenario Outline: Paged section redirect after creating an activity Given the following "courses" exist: | fullname | shortname | category | format | coursedisplay | numsections | startdate | | Course 1 | C1 | 0 | <courseformat> | 1 | 3 | 0 | And the following "activities" exist: | activity | course | name | | chat | C1 | Chat room | When I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I click on <section1> "link" in the <section1> "section" And I should see <section1> in the "div.single-section" "css_element" And I should see <section2> in the ".single-section div.nextsection" "css_element" And I should not see <prevunexistingsection> in the ".single-section" "css_element" Then I should see <section1> in the "div.single-section" "css_element" And I should see <section2> in the ".single-section div.nextsection" "css_element" And I should not see <prevunexistingsection> in the ".single-section" "css_element" Examples: | courseformat | section1 | section2 | prevunexistingsection | | topics | "Topic 1" | "Topic 2" | "Topic 0" | | weeks | "1 January - 7 January" | "8 January - 14 January" | "25 December - 31 December" | Scenario Outline: Weekly and topics course formats with Javascript disabled Given the following "courses" exist: | fullname | shortname | category | format | coursedisplay | numsections | startdate | | Course 1 | C1 | 0 | <courseformat> | 1 | 3 | 0 | And I log in as "admin" And I am on "Course 1" course homepage Then I click on <section2> "link" in the <section2> "section" And I am on "Course 1" course homepage And I click on <section3> "link" in the <section3> "section" And I am on "Course 1" course homepage And I click on <section1> "link" in the <section1> "section" And I should see <section1> in the "div.single-section" "css_element" And I should see <section2> in the ".single-section div.nextsection" "css_element" And I should not see <prevunexistingsection> in the ".single-section" "css_element" And I click on <section2> "link" in the ".single-section" "css_element" And I should see <section2> in the "div.single-section" "css_element" And I should see <section1> in the ".single-section div.prevsection" "css_element" And I should see <section3> in the ".single-section div.nextsection" "css_element" And I click on <section1> "link" in the ".single-section" "css_element" And I should see <section1> in the "div.single-section" "css_element" And I click on <section2> "link" in the ".single-section" "css_element" And I click on <section3> "link" in the ".single-section" "css_element" And I should see <section3> in the "div.single-section" "css_element" And I should see <section2> in the ".single-section div.prevsection" "css_element" And I should not see <section1> in the ".single-section .section-navigation" "css_element" And I should not see <prevunexistingsection> in the ".single-section" "css_element" And I should not see <nextunexistingsection> in the ".single-section" "css_element" Examples: | courseformat | section1 | section2 | section3 | prevunexistingsection | nextunexistingsection | | topics | "Topic 1" | "Topic 2" | "Topic 3" | "Topic 0" | "Topic 4" | | weeks | "1 January - 7 January" | "8 January - 14 January" | "15 January - 21 January" | "25 December - 31 December" | "22 January - 28 January" | other_users.feature 0000644 00000002573 15151776366 0010513 0 ustar 00 @core @core_course @javascript Feature: Test if displaying the course other users works correctly: As a user I need to see the other users who have permissions in a course without being enrolled. Background: Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | fullname | shortname | category | format | | Course 1 | C1 | CAT1 | topics | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | manager1 | Manager | 1 | manager1@example.com | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | Scenario: Test other users list in a course Given I log in as "admin" And I am on the "Course 1" "other users" page And I should see "Course 1: 0 other users" And the following "role assigns" exist: | user | role | contextlevel | reference | | manager1 | manager | System | | And I am on the "Course 1" "other users" page And I should see "Course 1: 1 other users" And I should see "Manager 1" course_search.feature 0000644 00000003124 15151776366 0010767 0 ustar 00 @core @core_course Feature: Courses can be searched for and moved in bulk. In order to manage a large number of courses As a Moodle Administrator I need to be able to search courses in bulk and move them around Background: Given the following "categories" exist: | name | category | idnumber | | Science | 0 | SCI | | English | 0 | ENG | | Miscellaneous | 0 | MISC | And the following "courses" exist: | fullname | shortname | category | | Biology Y1 | BIO1 | MISC | | Biology Y2 | BIO2 | MISC | | English Y1 | ENG1 | ENG | | English Y2 | ENG2 | MISC | Scenario: Search courses finds correct results Given I log in as "admin" And I go to the courses management page When I set the field "Search" to "Biology" And I press "Search" Then I should see "Biology Y1" And I should see "Biology Y2" And I should not see "English Y1" And I should not see "English Y2" @javascript Scenario: Search courses and move results in bulk Given I log in as "admin" And I go to the courses management page And I set the field "Search" to "Biology" And I press "Search" When I select course "Biology Y1" in the management interface And I select course "Biology Y2" in the management interface And I set the field "menumovecoursesto" to "Science" And I press "Move" Then I should see "Successfully moved 2 courses into Science" And I wait to be redirected And I click on category "Science" in the management interface And I should see "Biology Y1" And I should see "Biology Y2" create_delete_course.feature 0000644 00000012204 15151776366 0012306 0 ustar 00 @core @core_course Feature: Test we can both create and delete a course. As a Moodle admin I need to test I can create a course I need to test I can delete a course Scenario: Create a course Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "No courses in this category" in the "#course-listing" "css_element" And I click on "Create new course" "link" in the ".course-listing-actions" "css_element" And I set the following fields to these values: | Course full name | Test course: create a course | | Course short name | TCCAC | | Course ID number | TC3401 | | Course summary | This course has been created by automated tests. | And I press "Save and return" # Redirect And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Test course: create a course" in the "#course-listing" "css_element" Scenario: Delete a course via its management listing Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Test course: create a course | TCCAC | TC3401 | | CAT1 | Test course 2: create another course | TC2CAC | TC3402 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Test course: create a course" in the "#course-listing" "css_element" And I should see "Test course 2: create another course" in the "#course-listing" "css_element" And I click on "delete" action for "Test course: create a course" in management course listing # Redirect And I should see "Delete TCCAC" And I should see "Test course: create a course (TCCAC)" And I press "Delete" # Redirect And I should see "Deleting TCCAC" And I should see "TCCAC has been completely deleted" And I press "Continue" # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Test course 2: create another course" in the "#course-listing" "css_element" Scenario: Delete a course via its management details page Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Test course: create a course | TCCAC | TC3401 | | CAT1 | Test course 2: create another course | TC2CAC | TC3402 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Test course: create a course" in the "#course-listing" "css_element" And I should see "Test course 2: create another course" in the "#course-listing" "css_element" And I click on course "Test course: create a course" in the management interface # Redirect And I should see the "Course categories and courses" management page with a course selected And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Test course: create a course" in the "#course-listing" "css_element" And I should see "Test course 2: create another course" in the "#course-listing" "css_element" And I should see "Test course: create a course" in the "#course-detail" "css_element" And I click on "Delete" "link" in the ".course-detail-listing-actions" "css_element" # Redirect And I should see "Delete TCCAC" And I should see "Test course: create a course (TCCAC)" And I press "Delete" # Redirect And I should see "Deleting TCCAC" And I should see "TCCAC has been completely deleted" And I press "Continue" # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Test course 2: create another course" in the "#course-listing" "css_element" sectionzero_title.feature 0000644 00000004342 15151776366 0011712 0 ustar 00 @core @core_course Feature: Section 0 default/custom title In order to set up a course As a teacher I need to be able to use/change default section 0 title Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | data | Test database name | Test database description | C1 | database1 | 2 | | forum | Test forum name | Test forum name description | C1 | forum1 | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Default section 0 title is General Given I log in as "teacher1" When I am on "Course 1" course homepage with editing mode on Then I should see "General" in the "li#section-0" "css_element" @javascript Scenario: Editing section 0 title Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I edit the section "0" and I fill the form with: | Custom | 1 | | New value for Section name | Edited section 0 | And I should see "Edited section 0" in the "li#section-0" "css_element" When I set the field "Edit topic name" in the "li#section-0" "css_element" to "" Then I should not see "Edited section 0" in the "li#section-0" "css_element" And I should see "General" in the "li#section-0" "css_element" And "New name for topic" "field" should not exist And I set the field "Edit topic name" in the "li#section-0" "css_element" to "Edited section 0" And I should see "Edited section 0" in the "li#section-0" "css_element" And I edit the section "0" and I fill the form with: | Custom | 0 | And I should not see "Edited section 0" in the "li#section-0" "css_element" And I should see "General" in the "li#section-0" "css_element" course_browsing.feature 0000644 00000010771 15151776366 0011362 0 ustar 00 @core @core_course Feature: Restricting access to course lists In order to provide more targeted content As a Moodle Administrator I need to be able to give/revoke capabilities to view list of courses Background: Given the following "categories" exist: | name | category | idnumber | | Science category | 0 | SCI | | English category | 0 | ENG | | Other category | 0 | MISC | And the following "courses" exist: | fullname | shortname | category | | Biology Y1 | BIO1 | SCI | | Biology Y2 | BI02 | SCI | | English Y1 | ENG1 | ENG | | English Y2 | ENG2 | ENG | | Humanities Y1 | HUM2 | MISC | Given the following "users" exist: | username | firstname | lastname | email | | user0 | User | Z | user0@example.com | | userb | User | B | userb@example.com | | usere | User | E | usere@example.com | Given the following "roles" exist: | name | shortname | description | archetype | | Category viewer | coursebrowse | My custom role 1 | | Given I log in as "admin" And I set the following system permissions of "Authenticated user" role: | capability | permission | | moodle/category:viewcourselist | Prevent | And I set the following system permissions of "Guest" role: | capability | permission | | moodle/category:viewcourselist | Prevent | And I set the following system permissions of "Category viewer" role: | capability | permission | | moodle/category:viewcourselist | Allow | And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Navigation" block if not present And I log out And the following "role assigns" exist: | user | role | contextlevel | reference | | usere | coursebrowse | Category | ENG | | userb | coursebrowse | Category | ENG | | userb | coursebrowse | Category | SCI | Scenario: Browse courses as a user without any browse capability When I log in as "user0" And I am on site homepage Then I should not see "Available courses" And "Courses" "link" should not exist in the "Navigation" "block" And I log out Scenario: Browse own courses as a user without any browse capability Given the following "course enrolments" exist: | user | course | role | | user0 | BIO1 | student | When I log in as "user0" And I am on site homepage And I should see "Available courses" And I should see "Biology Y1" And "Courses" "link" should not exist in the "Navigation" "block" And I log out Scenario: Browse courses as a user who has access to only one category When I log in as "usere" And I am on site homepage Then I should see "Available courses" And I should see "English Y1" And I should see "English Y2" And I should not see "Biology" And I should not see "Humanities" And I click on "Courses" "link" in the "Navigation" "block" And "English category" "text" should exist in the ".breadcrumb" "css_element" And I should see "English Y1" And I should see "English Y2" And I should not see "Biology" And I should not see "Humanities" And I should not see "Other category" And I follow "English Y2" And I should see "You cannot enrol yourself in this course." And I log out Scenario: Browse courses as a user who has access to several but not all categories When I log in as "userb" And I am on site homepage Then I should see "Available courses" And I should see "English Y1" And I should see "English Y2" And I should see "Biology" And I should not see "Humanities" And I click on "Courses" "link" in the "Navigation" "block" # And "category" "text" should not exist in the ".breadcrumb" "css_element" And I should see "Science category" And I should see "English category" And I should not see "Other category" And I follow "Science category" And I should see "Biology Y2" And I should not see "English Y1" And the "Course categories" select box should contain "Science category" And the "Course categories" select box should contain "English category" And the "Course categories" select box should not contain "Other category" And I follow "Biology Y1" And I should see "You cannot enrol yourself in this course." And I log out course_creation.feature 0000644 00000013621 15151776366 0011331 0 ustar 00 @core @core_course Feature: Managers can create courses In order to group users and contents As a manager I need to create courses and set default values on them @javascript Scenario: Courses are created with the default announcements forum Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And I log in as "admin" And I create a course with: | Course full name | Course 1 | | Course short name | C1 | And I enrol "Teacher 1" user as "Teacher" And I enrol "Student 1" user as "Student" And I log out When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Latest announcements" block And I am on the Announcements "forum activity" page And "Add discussion topic" "link" should exist And I navigate to "Subscriptions" in current page administration And I should see "Forced subscription" And I log out And I am on the Announcements "forum activity" page logged in as student1 And "Add a new topic" "link" should not exist Scenario: Create a course from the management interface and return to it Given the following "courses" exist: | fullname | shortname | idnumber | startdate | enddate | | Course 1 | Course 1 | C1 | 957139200 | 960163200 | And I log in as "admin" And I go to the courses management page And I should see the "Categories" management page And I click on category "Category 1" in the management interface And I should see the "Course categories and courses" management page And I click on "Create new course" "link" in the "#course-listing" "css_element" When I set the following fields to these values: | Course full name | Course 2 | | Course short name | Course 2 | | Course summary | Course 2 summary | | id_startdate_day | 24 | | id_startdate_month | October | | id_startdate_year | 2015 | | id_enddate_day | 24 | | id_enddate_month | October | | id_enddate_year | 2016 | And I press "Save and return" Then I should see the "Course categories and courses" management page And I click on "Sort by Course time created ascending" "link" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I click on "Course 2" "link" in the "region-main" "region" And I click on "Edit" "link" in the ".course-detail" "css_element" And the following fields match these values: | Course full name | Course 2 | | Course short name | Course 2 | | Course summary | Course 2 summary | | id_startdate_day | 24 | | id_startdate_month | October | | id_startdate_year | 2015 | | id_enddate_day | 24 | | id_enddate_month | October | | id_enddate_year | 2016 | Scenario: Create a course as a custom course creator Given the following "users" exist: | username | firstname | lastname | email | | kevin | Kevin | the | kevin@example.com | And the following "roles" exist: | shortname | name | archetype | | creator | Creator | | And the following "system role assigns" exist: | user | role | contextlevel | | kevin | creator | System | And I log in as "admin" And I set the following system permissions of "Creator" role: | capability | permission | | moodle/course:create | Allow | | moodle/course:manageactivities | Allow | | moodle/course:viewparticipants | Allow | And I log out And I log in as "kevin" And I am on site homepage When I press "Add a new course" And I set the following fields to these values: | Course full name | My first course | | Course short name | myfirstcourse | And I press "Save and display" And I follow "Participants" Then I should see "My first course" And I should see "Participants" Scenario: Creators' role in new courses setting behavior Given the following "users" exist: | username | firstname | lastname | email | | kevin | Kevin | the | kevin@example.com | And the following "system role assigns" exist: | user | role | contextlevel | | kevin | coursecreator | System | And I log in as "admin" And I set the following administration settings values: | Creators' role in new courses | Non-editing teacher | And I log out And I log in as "kevin" And I am on site homepage When I press "Add a new course" And I set the following fields to these values: | Course full name | My first course | | Course short name | myfirstcourse | And I press "Save and display" And I click on "Participants" "link" Then I should see "Non-editing teacher" in the "Kevin the" "table_row" @javascript Scenario: Create a course as admin Given I log in as "admin" And the following config values are set as admin: | enroladminnewcourse | 0 | And I navigate to "Courses > Add a new course" in site administration And I set the following fields to these values: | Course full name | My first course | | Course short name | myfirstcourse | And I press "Save and display" And I navigate to course participants Then I should not see "Teacher" And I should see "Nothing to display" And the following config values are set as admin: | enroladminnewcourse | 1 | And I navigate to "Courses > Add a new course" in site administration And I set the following fields to these values: | Course full name | My second course | | Course short name | mysecondcourse | And I press "Save and display" And I navigate to course participants And I should see "Teacher" And I should not see "Nothing to display" course_change_visibility.feature 0000644 00000013447 15151776366 0013227 0 ustar 00 @core @core_course Feature: We can change the visibility of courses in the management interface. As a moodle admin I need to test hiding and then showing a course. I need to test hiding a course and then hiding and showing the category its within. # Test hiding and showing a course. Scenario: Test toggling course visibility through the management interfaces. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" And category in management listing should be visible "CAT1" And course in management listing should be visible "C1" And I toggle visibility of course "C1" in management listing # Redirect. And I should see the "Course categories and courses" management page with a course selected And category in management listing should be visible "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of course "C1" in management listing # Redirect. And I should see the "Course categories and courses" management page with a course selected And category in management listing should be visible "CAT1" And course in management listing should be visible "C1" And I toggle visibility of course "C1" in management listing # Redirect. And I should see the "Course categories and courses" management page with a course selected And category in management listing should be visible "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing # Redirect. And I should see the "Course categories and courses" management page And category in management listing should be dimmed "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing # Redirect. And I should see the "Course categories and courses" management page And category in management listing should be visible "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing # Test hiding and showing a course with JS, same as the above test. @javascript Scenario: Test using AJAX to hide a course through the management interfaces. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" And category in management listing should be visible "CAT1" And course in management listing should be visible "C1" And I toggle visibility of course "C1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of course "C1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And course in management listing should be visible "C1" And I toggle visibility of course "C1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be dimmed "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing And a new page should not have loaded since I started watching And category in management listing should be visible "CAT1" And course in management listing should be dimmed "C1" And I toggle visibility of category "CAT1" in management listing And I toggle visibility of course "C1" in management listing And I select "Courses" from the "Viewing" singleselect And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" And I toggle visibility of course "C1" in management listing And a new page should not have loaded since I started watching And course in management listing should be dimmed "C1" And I toggle visibility of course "C1" in management listing And course in management listing should be visible "C1" And a new page should not have loaded since I started watching course_category_breadcrumbs.feature 0000644 00000033303 15151776366 0013712 0 ustar 00 @javascript @core_course Feature: Course category breadcrumbs navigation To navigate around the course category pages As an admin user I should see breadcrumbs Background: Given the following "blocks" exist: | blockname | contextlevel | reference | defaultregion | | navigation | System | 1 | side-post | And the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | Scenario: Admin user navigates to 'course category management' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration When I follow "Cat 1" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Manage courses and categories" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Manage course categories and courses" in the "region-main" "region" Scenario: Admin user navigates to category 'view' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I click on "view" action for "Cat 1" in management category listing Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to 'add new course' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I click on "Create new course" "link" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Manage courses and categories" in the ".breadcrumb" "css_element" And I should see "Add a new course" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Add a new course" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to 'add category' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I click on "Create new category" "link" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Manage courses and categories" in the ".breadcrumb" "css_element" And I should see "Add a category" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Add new category" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to a subcategory 'management' page Given the following "categories" exist: | name | category | idnumber | | Subcat 1 | CAT1 | SUBCAT1 | And I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I follow "Subcat 1" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Subcat 1" in the ".breadcrumb" "css_element" And I should see "Manage courses and categories" in the ".breadcrumb" "css_element" And I should see "Subcat 1" in the ".page-context-header" "css_element" And I should see "Manage course categories and courses" in the "region-main" "region" Scenario: Admin user navigates to a subcategory 'view' page Given the following "categories" exist: | name | category | idnumber | | Subcat 1 | CAT1 | SUBCAT1 | And I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I follow "Subcat 1" When I click on "view" action for "Subcat 1" in management category listing Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Subcat 1" in the ".breadcrumb" "css_element" And I should see "Subcat 1" in the ".page-context-header" "css_element" And I should see "Subcat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to 'add new course' page within a subcategory Given the following "categories" exist: | name | category | idnumber | | Subcat 1 | CAT1 | SUBCAT1 | And I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I follow "Subcat 1" When I click on "Create new course" "link" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Subcat 1" in the ".breadcrumb" "css_element" And I should see "Manage courses and categories" in the ".breadcrumb" "css_element" And I should see "Add a new course" in the ".breadcrumb" "css_element" And I should see "Subcat 1" in the ".page-context-header" "css_element" And I should see "Add a new course" in the "region-main" "region" And I should see "Subcat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'settings' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I navigate to "Settings" in current page administration Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Settings" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Edit category settings" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'permissions' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I navigate to "Permissions" in current page administration Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Permissions" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Permissions in Category: Cat 1" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'assign roles' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I navigate to "Permissions" in current page administration When I select "Assign roles" from the "jump" singleselect Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Assign roles" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Assign roles in Category: Cat 1" in the "region-main" "region" Scenario: Admin user navigates to category 'check permissions' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I navigate to "Permissions" in current page administration When I select "Check permissions" from the "jump" singleselect Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Check permissions" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Check permissions in Category: Cat 1" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'cohorts' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I navigate to "Cohorts" in current page administration Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Cohorts" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Cohorts" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'add new cohort' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I navigate to "Cohorts" in current page administration When I follow "Add new cohort" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Cohorts" in the ".breadcrumb" "css_element" And I should see "Add new cohort" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Add new cohort" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'upload cohorts' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I navigate to "Cohorts" in current page administration When I follow "Upload cohorts" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Cohorts" in the ".breadcrumb" "css_element" And I should see "Upload cohorts" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Upload cohorts" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'filters' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I navigate to "Filters" in current page administration Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Filters" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Filter settings in Category: Cat 1" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'restore course' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I navigate to "Restore course" in current page administration Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Restore course" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Import a backup file" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'manage backup files' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" And I navigate to "Restore course" in current page administration When I press "Manage backup files" Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Restore course" in the ".breadcrumb" "css_element" And I should see "Manage backup files" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Manage backup files" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" Scenario: Admin user navigates to category 'content bank' page Given I log in as "admin" And I navigate to "Courses > Manage courses and categories" in site administration And I follow "Cat 1" When I navigate to "Content bank" in current page administration Then I should see "Courses" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".breadcrumb" "css_element" And I should see "Content bank" in the ".breadcrumb" "css_element" And I should see "Cat 1" in the ".page-context-header" "css_element" And I should see "Content bank" in the "region-main" "region" And I should see "Cat 1" in the ".block_navigation .active_tree_node" "css_element" course_activity_dates.feature 0000644 00000007122 15151776366 0012540 0 ustar 00 @core @core_course Feature: Allow teachers to edit the visibility of activity dates in a course In order to show students the activity dates in a course As a teacher I need to be able to edit activity dates settings Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | idnumber | name | intro | timeopen | timeclose | | choice | C1 | choice1 | Test choice | Test choice description | ##yesterday## | ##tomorrow## | Scenario: Activity dates setting can be enabled to display activity dates in a course Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I navigate to "Settings" in current page administration When I set the following fields to these values: | Show activity dates | Yes | And I click on "Save and display" "button" And I follow "Test choice" Then the activity date information in "Test choice" should exist And the activity date in "Test choice" should contain "Opened:" And the activity date in "Test choice" should contain "Closes:" And I am on "Course 1" course homepage # When showactivitydates is enabled, activity dates should be shown on the course homepage. And the activity date information in "Test choice" should exist And the activity date in "Test choice" should contain "Opened:" And the activity date in "Test choice" should contain "Closes:" Scenario: Activity dates setting can be disabled to hide activity dates in a course Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I navigate to "Settings" in current page administration When I set the following fields to these values: | Show activity dates | No | And I click on "Save and display" "button" And I follow "Test choice" # Activity dates are always shown in the module's view page. Then the activity date information in "Test choice" should exist And the activity date in "Test choice" should contain "Opened:" And the activity date in "Test choice" should contain "Closes:" And I am on "Course 1" course homepage # When showactivitydates is disabled, activity dates should not be shown on the course homepage. And the activity date information in "Test choice" should not exist Scenario: Default activity dates setting default value can changed to No Given I log in as "admin" And I navigate to "Courses > Course default settings" in site administration When I set the following fields to these values: | Show activity dates | No | And I click on "Save changes" "button" And I navigate to "Courses > Add a new course" in site administration Then the field "showactivitydates" matches value "No" Scenario: Default activity dates setting default value can changed to Yes Given I log in as "admin" And I navigate to "Courses > Course default settings" in site administration When I set the following fields to these values: | Show activity dates | Yes | And I click on "Save changes" "button" And I navigate to "Courses > Add a new course" in site administration Then the field "showactivitydates" matches value "Yes" activity_navigation_with_restrictions.feature 0000644 00000006610 15151776366 0016063 0 ustar 00 @core @core_course Feature: Activity navigation involving activities with access restrictions In order to quickly switch to another activity that has access restrictions As a student I need to be able to use the activity navigation feature to access the activity after satisfying its access conditions Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | enablecompletion | | Course 1 | C1 | topics | 1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | page | Page 1 | Test page description 1 | C1 | page1 | 0 | | page | Page 2 | Test page description 2 | C1 | page2 | 0 | | page | Page 3 | Test page description 3 | C1 | page3 | 0 | | page | Page 4 | Test page description 4 | C1 | page4 | 0 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Set completion for Page 2. And I open "Page 2" actions menu And I click on "Edit settings" "link" in the "Page 2" activity And I expand all fieldsets And I set the field "Completion tracking" to "Show activity as complete when conditions are met" And I set the following fields to these values: | Completion tracking | Show activity as complete when conditions are met | | Require view | 1 | And I press "Save and return to course" # Require Page 2 to be completed first before Page 3 can be accessed. And I open "Page 3" actions menu And I click on "Edit settings" "link" in the "Page 3" activity And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the field "Activity or resource" to "Page 2" And I press "Save and return to course" And I log out @javascript Scenario: Activity navigation involving activities with access restrictions Given I am on the "Page 1" "page activity" page logged in as student1 Then I should see "Page 2" in the "#next-activity-link" "css_element" # Activity that has access restriction should not show up in the dropdown. And the "Jump to..." select box should not contain "Page 3" And I select "Page 4" from the "Jump to..." singleselect # Page 2 should be shown in the previous link since Page 3 is not yet available. And I should see "Page 2" in the "#prev-activity-link" "css_element" And the "Jump to..." select box should not contain "Page 3" # Navigate to Page 2. And I click on "Page 2" "link" in the "page-content" "region" # Since Page 2 has now been viewed and deemed completed, Page 3 can now be accessed. And I should see "Page 3" in the "#next-activity-link" "css_element" And the "Jump to..." select box should contain "Page 3" customfields_visibility.feature 0000644 00000007067 15151776366 0013124 0 ustar 00 @core @core_course @core_customfield @javascript Feature: The visibility of fields control where they are displayed In order to display custom fields on course listing As a manager I can change the visibility of the fields Background: Given the following "custom field categories" exist: | name | component | area | itemid | | Category for test | core_course | course | 0 | Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Display course custom fields on homepage When I log in as "admin" And I navigate to "Courses > Course custom fields" in site administration And I click on "Add a new custom field" "link" And I click on "Short text" "link" And I set the following fields to these values: | Name | Test field | | Short name | testfield | | Visible to | Everyone | And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" And I log out Then I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Test field | testcontent | And I press "Save and display" And I am on site homepage Then I should see "Test field: testcontent" Scenario: Do not display course custom fields on homepage When I log in as "admin" And I navigate to "Courses > Course custom fields" in site administration And I click on "Add a new custom field" "link" And I click on "Short text" "link" And I set the following fields to these values: | Name | Test field | | Short name | testfield | | Visible to | Nobody | And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" And I log out When I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Test field | testcontent | And I press "Save and display" And I am on site homepage And I should not see "Test field: testcontent" Scenario: Display course custom fields on homepage only to course editors When I log in as "admin" And I navigate to "Courses > Course custom fields" in site administration And I click on "Add a new custom field" "link" And I click on "Short text" "link" And I set the following fields to these values: | Name | Test field | | Short name | testfield | | Visible to | Teachers | And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" And I log out When I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Test field | testcontent | And I press "Save and display" When I am on site homepage And I should see "Test field: testcontent" And I log out When I log in as "student" When I am on site homepage And I should not see "Test field: testcontent" section_visibility.feature 0000644 00000013546 15151776366 0012066 0 ustar 00 @core @core_course @_cross_browser Feature: Show/hide course sections In order to delay sections availability As a teacher I need to show or hide sections Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | hiddensections | | Course 1 | C1 | topics | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add a "Forum" to section "1" and I fill the form with: | Forum name | Test hidden forum 11 name | | Description | Test hidden forum 11 description | | Availability | Hide on course page | And I add a "Forum" to section "1" and I fill the form with: | Forum name | Test hidden forum 12 name | | Description | Test hidden forum 12 description | | Availability | Show on course page | And I add a "Forum" to section "2" and I fill the form with: | Forum name | Test hidden forum 21 name | | Description | Test hidden forum 21 description | | Availability | Hide on course page | And I add a "Forum" to section "2" and I fill the form with: | Forum name | Test hidden forum 22 name | | Description | Test hidden forum 22 description | | Availability | Show on course page | And I add a "Forum" to section "3" and I fill the form with: | Forum name | Test hidden forum 31 name | | Description | Test hidden forum 31 description | | Availability | Hide on course page | And I add a "Forum" to section "3" and I fill the form with: | Forum name | Test hidden forum 32 name | | Description | Test hidden forum 32 description | | Availability | Show on course page | @javascript Scenario: Show / hide section icon functions correctly Given I am on "Course 1" course homepage When I hide section "1" Then section "1" should be hidden And section "2" should be visible And section "3" should be visible And I hide section "2" And section "2" should be hidden And I show section "2" And section "2" should be visible And I hide section "3" And I show section "3" And I hide section "3" And section "3" should be hidden And I reload the page And section "1" should be hidden And all activities in section "1" should be hidden And section "2" should be visible And section "3" should be hidden And all activities in section "1" should be hidden And I log out And I log in as "student1" And I am on "Course 1" course homepage And section "1" should be hidden And all activities in section "1" should be hidden And section "2" should be visible And section "3" should be hidden And all activities in section "1" should be hidden @javascript Scenario: Students can not navigate to hidden sections Given I am on "Course 1" course homepage And I hide section "2" Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" When I click on "Topic 1" "link" in the "region-main" "region" Then I should see "Topic 2" in the "region-main" "region" And I click on "Topic 2" "link" in the "region-main" "region" And I should see "Topic 1" in the "region-main" "region" And I should see "Topic 3" in the "region-main" "region" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I click on "Topic 1" "link" in the "region-main" "region" And I should not see "Topic 2" in the "region-main" "region" And I should see "Topic 3" in the "region-main" "region" And I click on "Topic 3" "link" in the "region-main" "region" And I should not see "Topic 2" in the "region-main" "region" And I should see "Topic 1" in the "region-main" "region" @javascript Scenario: Students can not navigate to restricted sections Given I am on "Course 1" course homepage Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | | Enable completion tracking | Yes | And I press "Save and display" And the following "activities" exist: | activity | course | section | name | completion | | label | C1 | 1 | Test label | 1 | And I edit the section "2" And I expand all fieldsets And I click on "Add restriction..." "button" And I click on "Activity completion" "button" in the "Add restriction..." "dialogue" And I set the following fields to these values: | cm | Test label | | Required completion status | must be marked complete | And I press "Save changes" When I click on "Topic 1" "link" in the "region-main" "region" Then I should see "Topic 2" in the "region-main" "region" And I click on "Topic 2" "link" in the "region-main" "region" And I should see "Topic 1" in the "region-main" "region" And I should see "Topic 3" in the "region-main" "region" And I log out And I log in as "student1" And I am on "Course 1" course homepage And I click on "Topic 1" "link" in the "region-main" "region" And I should not see "Topic 2" in the "region-main" "region" And I should see "Topic 3" in the "region-main" "region" And I click on "Topic 3" "link" in the "region-main" "region" And I should not see "Topic 2" in the "region-main" "region" And I should see "Topic 1" in the "region-main" "region" course_download_content_permissions.feature 0000644 00000013241 15151776366 0015517 0 ustar 00 @core @core_course Feature: Access to downloading course content can be controlled In order to allow or restrict access to download course content As a trusted user I can control access to the download course content feature Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | | Hockey 101 | C1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And I log in as "admin" And the following config values are set as admin: | downloadcoursecontentallowed | 1 | And I log out Scenario: Site admins can remove the download course content feature Given I log in as "admin" And I am on "Hockey 101" course homepage And I navigate to "Settings" in current page administration And I set the field "Enable download course content" to "Yes" And I press "Save and display" Then "Download course content" "link" should exist in current page administration When the following config values are set as admin: | downloadcoursecontentallowed | 0 | And I am on "Hockey 101" course homepage Then "Download course content" "link" should not exist in current page administration And I navigate to "Settings" in current page administration And I should not see "Enable download course content" Scenario: Site admins can set the default value for whether download course content is enabled in courses Given I log in as "admin" And I am on "Hockey 101" course homepage And "Download course content" "link" should not exist in current page administration When I navigate to "Courses > Course default settings" in site administration And I set the field "Enable download course content" to "Yes" And I press "Save changes" And I am on "Hockey 101" course homepage Then "Download course content" "link" should exist in current page administration Scenario: A teacher can enable and disable the download course content feature when it is available Given I log in as "teacher1" When I am on "Hockey 101" course homepage And "Download course content" "link" should not exist in current page administration And I navigate to "Settings" in current page administration And I should see "Enable download course content" And I set the field "Enable download course content" to "Yes" And I press "Save and display" Then "Download course content" "link" should exist in current page administration And I navigate to "Settings" in current page administration And I set the field "Enable download course content" to "No" And I press "Save and display" Then "Download course content" "link" should not exist in current page administration Scenario: Teachers require a capability to access the download course content feature or modify its availability in a course Given I log in as "admin" And I navigate to "Courses > Course default settings" in site administration And I set the field "Enable download course content" to "Yes" And I press "Save changes" And I log out # Check teacher can see download option and enable dropdown. And I log in as "teacher1" And I am on "Hockey 101" course homepage Then "Download course content" "link" should exist in current page administration And I navigate to "Settings" in current page administration And "Enable download course content" "select" should exist And I log out # Remove teacher's capabilities for download course content. And I log in as "admin" And I set the following system permissions of "Teacher" role: | capability | permission | | moodle/course:downloadcoursecontent | Prohibit | | moodle/course:configuredownloadcontent | Prohibit | And I log out # Check teacher can no longer see download option, and that enable value is visible, but dropdown no longer available. When I log in as "teacher1" And I am on "Hockey 101" course homepage Then "Download course content" "link" should not exist in current page administration And I navigate to "Settings" in current page administration And I should see "Enable download course content" And I should see "Site default (Yes)" And "Enable download course content" "select" should not exist Scenario: Students require a capability to access the download course content feature in a course Given I log in as "teacher1" And I am on "Hockey 101" course homepage And I navigate to "Settings" in current page administration And I set the field "Enable download course content" to "Yes" And I press "Save and display" And I log out # Check student can see the download link. And I log in as "student1" And I am on "Hockey 101" course homepage And "Download course content" "link" should exist in current page administration And I log out And I log in as "admin" # Remove student's capability for download course content. When I set the following system permissions of "Student" role: | capability | permission | | moodle/course:downloadcoursecontent | Prohibit | And I log out # Check student can no longer see the download link. And I log in as "student1" And I am on "Hockey 101" course homepage Then "Download course content" "link" should not exist in current page administration course_resort.feature 0000644 00000026437 15151776366 0011054 0 ustar 00 @core @core_course Feature: Test we can resort course in the management interface. As a moodle admin I need to test we can resort courses within a category. I need to test we can manually sort courses. # Test resorting courses with Scenario Outline: Resort courses. Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | sortorder | timecreated | | CAT1 | Social studies | Senior school | Ext003 | 1 | 1000000001 | | CAT1 | Applied sciences | Middle school | Sci001 | 2 | 1000000002 | | CAT1 | Extended social studies | Junior school | Ext002 | 3 | 1000000003 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect. And I should see the "Course categories and courses" management page And I should see "Sort courses" in the ".course-listing-actions" "css_element" And I should see "Sort by Course full name ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course full name descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course short name ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course short name descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course ID number ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course ID number descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course time created ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element" And I click on <sortby> "link" in the ".course-listing-actions" "css_element" # Redirect. And I should see the "Course categories and courses" management page And I should see course listing <course1> before <course2> And I should see course listing <course2> before <course3> Examples: | sortby | course1 | course2 | course3 | | "Sort by Course full name ascending" | "Applied sciences" | "Extended social studies" | "Social studies" | | "Sort by Course full name descending" | "Social studies" | "Extended social studies" | "Applied sciences" | | "Sort by Course short name ascending" | "Extended social studies" | "Applied sciences" | "Social studies" | | "Sort by Course short name descending" | "Social studies" | "Applied sciences" | "Extended social studies" | | "Sort by Course ID number ascending" | "Extended social studies" | "Social studies" | "Applied sciences" | | "Sort by Course ID number descending" | "Applied sciences" | "Social studies" | "Extended social studies" | | "Sort by Course time created ascending" | "Social studies" | "Applied sciences" | "Extended social studies" | | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences" | "Social studies" | @javascript Scenario Outline: Resort courses with JavaScript enabled. Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | sortorder | timecreated | | CAT1 | Social studies | Senior school | Ext003 | 1 | 1000000001 | | CAT1 | Applied sciences | Middle school | Sci001 | 2 | 1000000002 | | CAT1 | Extended social studies | Junior school | Ext002 | 3 | 1000000003 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Sort courses" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course full name ascending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course full name descending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course short name ascending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course short name descending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course ID number ascending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course ID number descending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course time created ascending" in the ".course-listing-actions" "css_element" And I should not see "Sort by Course time created descending" in the ".course-listing-actions" "css_element" And I open the action menu in ".course-listing-actions" "css_element" And I should see "Sort by Course full name ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course full name descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course short name ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course short name descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course ID number ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course ID number descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course time created ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element" And I click on <sortby> "link" in the ".course-listing-actions" "css_element" And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see course listing <course1> before <course2> And I should see course listing <course2> before <course3> Examples: | sortby | course1 | course2 | course3 | | "Sort by Course full name ascending" | "Applied sciences" | "Extended social studies" | "Social studies" | | "Sort by Course full name descending" | "Social studies" | "Extended social studies" | "Applied sciences" | | "Sort by Course short name ascending" | "Extended social studies" | "Applied sciences" | "Social studies" | | "Sort by Course short name descending" | "Social studies" | "Applied sciences" | "Extended social studies" | | "Sort by Course ID number ascending" | "Extended social studies" | "Social studies" | "Applied sciences" | | "Sort by Course ID number descending" | "Applied sciences" | "Social studies" | "Extended social studies" | | "Sort by Course time created ascending" | "Social studies" | "Applied sciences" | "Extended social studies" | | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences" | "Social studies" | Scenario: Test moving courses up and down by one. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect. And I should see the "Course categories and courses" management page And I should see "Course categories" in the "#category-listing h3" "css_element" And I should see "Cat 1" in the "#category-listing" "css_element" And I open the action menu in ".course-listing-actions" "css_element" And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element" # Redirect. And I should see the "Course categories and courses" management page And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 2" before "Course 3" And I click to move course "C1" down one # Redirect. And I should see the "Course categories and courses" management page with a course selected And I should see course listing "Course 2" before "Course 1" And I should see course listing "Course 1" before "Course 3" And I click to move course "C3" up one # Redirect. And I should see the "Course categories and courses" management page with a course selected And I should see course listing "Course 2" before "Course 3" And I should see course listing "Course 3" before "Course 1" # Like the above test but with JavaScript enabled. @javascript Scenario: Test using AJAX to move courses up and down by one. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Course categories" in the "#category-listing h3" "css_element" And I should see "Cat 1" in the "#category-listing" "css_element" And I open the action menu in ".course-listing-actions" "css_element" And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 2" before "Course 3" And I click to move course "C1" down one And a new page should not have loaded since I started watching And I should see course listing "Course 2" before "Course 1" And I should see course listing "Course 1" before "Course 3" And I click to move course "C3" up one And a new page should not have loaded since I started watching And I should see course listing "Course 2" before "Course 3" And I should see course listing "Course 3" before "Course 1" search_recommended_activities.feature 0000644 00000002001 15151776366 0014166 0 ustar 00 @core @core_course Feature: Search recommended activities As an admin I am able to search for activities in the "Recommended activities" admin setting page Scenario: Search results are returned if the search query matches any activity names Given I log in as "admin" And I am on site homepage And I navigate to "Courses > Activity chooser > Recommended activities" in site administration When I set the field "search" to "assign" And I click on "Submit search" "button" Then I should see "Search results" And "Assignment" "table_row" should exist And "Book" "table_row" should not exist Scenario: Search results are not returned if the search query does not match with any activity names Given I log in as "admin" And I am on site homepage And I navigate to "Courses > Activity chooser > Recommended activities" in site administration When I set the field "search" to "random query" And I click on "Submit search" "button" Then I should see "Search results: 0" course_download_content.feature 0000644 00000004426 15151776367 0013072 0 ustar 00 @core @core_course Feature: Course content can be downloaded In order to retain a backup offline copy of course activity/resource data As a user I can download a course's content Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | | Hockey 101 | C1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And I log in as "admin" And I navigate to "Courses > Download course content" in site administration And I set the following fields to these values: | Download course content feature available | 1 | And I press "Save changes" And I navigate to "Courses > Course default settings" in site administration And I set the field "Enable download course content" to "Yes" And I press "Save changes" And I log out @javascript Scenario: A student can download course content when the feature is enabled in their course Given I log in as "student1" When I am on "Hockey 101" course homepage And I navigate to "Download course content" in current page administration Then I should see "You are about to download a zip file" # Without the ability to check the downloaded file, the absence of an exception being thrown here is considered a success. And I click on "Download" "button" in the "Download course content" "dialogue" @javascript Scenario: A teacher can download course content when the feature is enabled in their course Given I log in as "teacher1" When I am on "Hockey 101" course homepage And "Download course content" "link" should exist in current page administration And I navigate to "Download course content" in current page administration Then I should see "You are about to download a zip file" # Without the ability to check the downloaded file, the absence of an exception being thrown here is considered a success. And I click on "Download" "button" in the "Download course content" "dialogue" recommend_activities.feature 0000644 00000004005 15151776367 0012337 0 ustar 00 @core @core_course @javascript Feature: Recommending activities As an admin I want to recommend activities in the activity chooser Scenario: As an admin I can recommend activities from an admin setting page. Given I log in as "admin" And I am on site homepage And I navigate to "Courses > Activity chooser > Recommended activities" in site administration And I click on ".activity-recommend-checkbox" "css" in the "Assignment" "table_row" And I navigate to "Courses > Add a new course" in site administration When I navigate to "Courses > Activity chooser > Recommended activities" in site administration Then "input[aria-label=\"Recommend activity: Assignment\"][checked=checked]" "css_element" should exist And "input[aria-label=\"Recommend activity: Book\"]:not([checked=checked])" "css_element" should exist Scenario: As an admin I can remove recommend activities from an admin setting page. Given I log in as "admin" And I am on site homepage And I navigate to "Courses > Activity chooser > Recommended activities" in site administration And I click on ".activity-recommend-checkbox" "css" in the "Assignment" "table_row" And I navigate to "Courses > Add a new course" in site administration And I navigate to "Courses > Activity chooser > Recommended activities" in site administration And "input[aria-label=\"Recommend activity: Assignment\"][checked=checked]" "css_element" should exist And "input[aria-label=\"Recommend activity: Book\"]:not([checked=checked])" "css_element" should exist And I click on ".activity-recommend-checkbox" "css" in the "Assignment" "table_row" And I navigate to "Courses > Add a new course" in site administration When I navigate to "Courses > Activity chooser > Recommended activities" in site administration Then "input[aria-label=\"Recommend activity: Assignment\"]:not([checked=checked])" "css_element" should exist And "input[aria-label=\"Recommend activity: Book\"]:not([checked=checked])" "css_element" should exist rename_roles.feature 0000644 00000005547 15151776367 0010631 0 ustar 00 @core @core_course Feature: Rename roles within a course In order to set course roles names according to their responsabilities As a teacher I need to edit the course role names @javascript Scenario: Rename roles within a course Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | teacher2 | Teacher | 2 | teacher2@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher2 | C1 | teacher | | student1 | C1 | student | And I log in as "teacher1" And I am on "Course 1" course homepage When I navigate to "Settings" in current page administration And I set the following fields to these values: | Your word for 'Non-editing teacher' | Tutor | | Your word for 'Student' | Learner | And I press "Save and display" And I follow "Switch role to..." in the user menu Then "Tutor" "button" should exist And "Learner" "button" should exist And I navigate to course participants And I set the field "type" in the "Filter 1" "fieldset" to "Roles" And I open the autocomplete suggestions list in the "Filter 1" "fieldset" And I should see "Learner (Student)" in the ".form-autocomplete-suggestions" "css_element" And I press the escape key And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Tutor (Non-editing teacher)" And I click on "Student 1's role assignments" "link" And I open the autocomplete suggestions list in the "Student 1" "table_row" And "Tutor (Non-editing teacher)" "autocomplete_suggestions" should exist And I click on "Cancel" "link" And I press "Enrol users" And the "Assign role" select box should contain "Learner (Student)" And I click on "Cancel" "button" in the "Enrol users" "dialogue" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Your word for 'Non-editing teacher' | | | Your word for 'Student' | | And I press "Save and display" And I follow "Switch role to..." in the user menu And I should see "Teacher" And "Student" "button" should exist And "Learner" "button" should not exist And I navigate to course participants And I set the field "type" in the "Filter 1" "fieldset" to "Roles" And I open the autocomplete suggestions list in the "Filter 1" "fieldset" And I should see "Non-editing teacher" in the ".form-autocomplete-suggestions" "css_element" And I should see "Student" in the ".form-autocomplete-suggestions" "css_element" coursetags.feature 0000644 00000010177 15151776367 0010330 0 ustar 00 @core @core_course @core_tag @javascript Feature: Tagging courses In order to search courses As a teacher I need to be able to tag courses Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | teacher2 | Teacher | 2 | teacher2@example.com | | user1 | User | 1 | user1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | c1 | | Course 2 | c2 | And the following "tags" exist: | name | isstandard | | Neverusedtag | 1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | c1 | editingteacher | | teacher2 | c1 | teacher | | teacher1 | c2 | editingteacher | | teacher2 | c2 | teacher | And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Tags | Mathematics | And I press "Save and display" And I log out Scenario: Set course tags using the course edit form When I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I expand all fieldsets Then "Mathematics" "autocomplete_suggestions" should exist And I set the following fields to these values: | Tags | Mathematics, Algebra | And I press "Save and display" And I am on "Course 2" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Tags | Mathematics, Geometry | And I press "Save and display" And I log out And I log in as "user1" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| # TODO MDL-57120 "Tags" link not accessible without navigation block. And I add the "Navigation" block if not present And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Tags" "link" in the "Navigation" "block" And I follow "Mathematics" Then I should see "Course 1" And I should see "Course 2" And I follow "Tags" And I follow "Algebra" And I should see "Course 1" And I should not see "Course 2" And I follow "Tags" And I follow "Geometry" And I should not see "Course 1" And I should see "Course 2" And I log out Scenario: User can set course tags using separate form Given I log in as "admin" And I set the following system permissions of "Non-editing teacher" role: | moodle/course:tag | Allow | And I log out When I log in as "teacher2" And I am on "Course 1" course homepage And I navigate to "Course tags" in current page administration Then I should see "Mathematics" in the ".form-autocomplete-selection" "css_element" And I set the following fields to these values: | Tags | Mathematics, Algebra | And I press "Save changes" And I am on "Course 2" course homepage And I navigate to "Course tags" in current page administration And I set the following fields to these values: | Tags | Mathematics, Geometry | And I press "Save changes" And I log out And I log in as "user1" And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| # TODO MDL-57120 "Tags" link not accessible without navigation block. And I add the "Navigation" block if not present And I click on "Site pages" "list_item" in the "Navigation" "block" And I click on "Tags" "link" in the "Navigation" "block" And I follow "Mathematics" Then I should see "Course 1" And I should see "Course 2" And I follow "Tags" And I follow "Algebra" And I should see "Course 1" And I should not see "Course 2" And I follow "Tags" And I follow "Geometry" And I should not see "Course 1" And I should see "Course 2" And I log out move_sections.feature 0000644 00000005045 15151776367 0011024 0 ustar 00 @core @core_course Feature: Sections can be moved In order to rearrange my course contents As a teacher I need to move sections up and down Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | | Course 1 | C1 | topics | 0 | 5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | forum | Test forum name | Test forum name description | C1 | forum1 | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Move up and down a section with Javascript disabled in a single page course When I move down section "1" Then I should see "Test forum name" in the "Topic 2" "section" And I move up section "2" And I should see "Test forum name" in the "Topic 1" "section" Scenario: Move up and down a section with Javascript disabled in the course home of a course using paged mode Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" When I move down section "1" Then I should see "Test forum name" in the "Topic 2" "section" And I move up section "2" And I should see "Test forum name" in the "Topic 1" "section" Scenario: Sections can not be moved with Javascript disabled in a section page of a course using paged mode Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" When I follow "Topic 2" Then "Topic 1" "section" should not exist And "Topic 3" "section" should not exist And "Move down" "link" should not exist And "Move up" "link" should not exist @javascript Scenario: Move section with javascript When I open section "1" edit menu And I click on "Move" "link" in the "Topic 1" "section" And I click on "Topic 3" "link" in the ".modal-body" "css_element" Then I should see "Test forum name" in the "Topic 3" "section" course_request.feature 0000644 00000011762 15151776367 0011222 0 ustar 00 @core @core_course @javascript Feature: Users can request and approve courses As a moodle admin In order to improve course creation process I need to be able to enable course approval Background: Given the following "users" exist: | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | | user3 | User | 3 | user3@example.com | Scenario: Simple course request workflow Given the following "system role assigns" exist: | user | course | role | | user2 | Acceptance test site | manager | And the following config values are set as admin: | lockrequestcategory | 1 | Given I log in as "admin" And I set the following system permissions of "Authenticated user" role: | capability | permission | | moodle/course:request | Allow | And I log out When I log in as "user1" And I am on course index And I click on "More actions" "button" And I click on "Request a course" "link" And I set the following fields to these values: | Course full name | My new course | | Course short name | Mynewcourse | | Supporting information | pretty please | And I press "Request a course" And I should see "Your course request has been saved successfully." And I press "Continue" And I am on course index And I should not see "My new course" And I log out And I log in as "user2" And I am on course index And I click on "More actions" "button" And I click on "Courses pending approval" "link" And the following should exist in the "pendingcourserequests" table: | Requested by | Course short name | Course full name | Category | Reason for course request | | User 1 | Mynewcourse | My new course | Category 1 | pretty please | And I click on "Approve" "button" in the "My new course" "table_row" And I press "Save and return" And I should see "There are no courses pending approval" And I press "Back to course listing" And I should see "My new course" And I log out And I log in as "user1" And I am on course index And I follow "My new course" And I navigate to course participants And I should see "Teacher" in the "User 1" "table_row" And I log out Scenario: Course request with category selection Given the following "categories" exist: | name | category | idnumber | | Science category | 0 | SCI | | English category | 0 | ENG | | Other category | 0 | MISC | Given the following "roles" exist: | name | shortname | description | archetype | | Course requestor | courserequestor | My custom role 1 | | And the following "role assigns" exist: | user | role | contextlevel | reference | | user1 | courserequestor | Category | SCI | | user1 | courserequestor | Category | ENG | | user2 | manager | Category | SCI | | user3 | manager | Category | ENG | Given I log in as "admin" And I set the following system permissions of "Course requestor" role: | capability | permission | | moodle/course:request | Allow | And I log out And I log in as "user1" And I am on course index And I follow "English category" And I click on "More actions" "button" And I click on "Request a course" "link" And I should see "English category" in the ".form-autocomplete-selection" "css_element" And I set the following fields to these values: | Course full name | My new course | | Course short name | Mynewcourse | | Supporting information | pretty please | And I press "Request a course" And I log out And I log in as "user2" And I am on course index And I follow "English category" And I should not see "More" in the "region-main" "region" And I should not see "Courses pending approval" And I am on course index And I follow "Science category" And I click on "More actions" "button" And I click on "Courses pending approval" "link" And I should not see "Mynewcourse" And I press "Back to course listing" And I log out And I log in as "user3" And I am on course index And I follow "English category" And I click on "More actions" "button" And I click on "Courses pending approval" "link" And the following should exist in the "pendingcourserequests" table: | Requested by | Course short name | Course full name | Category | Reason for course request | | User 1 | Mynewcourse | My new course | English category | pretty please | And I click on "Approve" "button" in the "Mynewcourse" "table_row" And I press "Save and return" And I am on course index And I follow "English category" And I should see "My new course" And I log out navigate_course_list.feature 0000644 00000006370 15151776367 0012362 0 ustar 00 @core @core_course Feature: Browse course list and return back from enrolment page In order to navigate between course list consistently As a user I need to be able to return back from enrolment page Background: Given the following "users" exist: | username | firstname | lastname | email | | user1 | User | 1 | user1@example.com | | user2 | User | 2 | user2@example.com | And the following "categories" exist: | name | category | idnumber | | Sample category | 0 | CAT1 | And the following "courses" exist: | fullname | shortname | category | | Sample course | C1 | 0 | | Course 1 | COURSE1 | CAT1 | Scenario: A user can return to the category page from enrolment page When I log in as "user2" And I am on course index And I follow "Category 1" And I follow "Sample course" And I press "Continue" Then I should see "Courses" in the ".breadcrumb" "css_element" And I click on "Courses" "link" in the ".breadcrumb" "css_element" And I follow "Sample category" And I am on "Course 1" course homepage And I press "Continue" And I should see "Sample category" in the ".breadcrumb" "css_element" @javascript Scenario: A user can return to the previous page from enrolment page by clicking navigation links Given I log in as "admin" And I am on site homepage And I turn editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Navigation" block if not present And I configure the "Navigation" block And I set the following fields to these values: | Page contexts | Display throughout the entire site | And I press "Save changes" And I log out When I log in as "user2" And I change window size to "large" And I open my profile in edit mode And I expand "Courses" node And I expand "Sample category" node And I follow "Course 1" And I press "Continue" Then I should see "Edit profile" in the ".breadcrumb" "css_element" Scenario: User can return to the choice activity from enrolment page Given the following "roles" exist: | name | shortname | description | archetype | | Non-enrolled | custom1 | My custom role 1 | user | And the following "role assigns" exist: | user | role | contextlevel | reference | | user1 | custom1 | Course | C1 | And the following "activities" exist: | activity | name | intro | course | idnumber | | choice | Test choice | Test choice description | C1 | choice1 | And I log in as "admin" And I set the following system permissions of "Non-enrolled" role: | capability | permission | | moodle/course:view | Allow | And I log out When I log in as "user1" And I am on course index And I follow "Category 1" And I follow "Sample course" And I follow "Test choice" And I should see "Sorry, only enrolled users are allowed to make choices." And I press "Enrol me in this course" And I press "Continue" Then I should see "Test choice" in the ".breadcrumb" "css_element" move_activities.feature 0000644 00000005274 15151776367 0011345 0 ustar 00 @core @core_course Feature: Activities can be moved between sections In order to rearrange my course contents As a teacher I need to move activities between sections Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | | Course 1 | C1 | topics | 0 | 5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activity" exists: | activity | forum | | course | C1 | | idnumber | 00001 | | name | Test forum name | | intro | Test forum description | | section | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on Scenario: Move activities in a single page course with Javascript disabled When I move "Test forum name" activity to section "2" Then I should see "Test forum name" in the "Topic 2" "section" And I should not see "Test forum name" in the "Topic 1" "section" Scenario: Move activities in the course home with Javascript disabled using paged mode Given I navigate to "Settings" in current page administration And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" When I move "Test forum name" activity to section "2" Then I should see "Test forum name" in the "Topic 2" "section" And I should not see "Test forum name" in the "Topic 1" "section" Scenario: Move activities in a course section with Javascript disabled using paged mode Given I navigate to "Settings" in current page administration And the following "activity" exists: | activity | forum | | course | C1 | | idnumber | 00002 | | name | Second forum name | | intro | Second forum description | | section | 1 | And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" And I follow "Topic 1" When I move "Second forum name" activity to section "1" Then "Second forum name" "link" should appear before "Test forum name" "link" @javascript Scenario: Move activity with javascript When I move "Test forum name" activity to section "3" Then I should see "Test forum name" in the "Topic 3" "section" view_subfolders_inline.feature 0000644 00000006041 15151776367 0012704 0 ustar 00 @core @core_course Feature: View subfolders in a course in-line In order to provide different view options for folders As a teacher I need to add a folders and subfolders and view them inline Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | | Course 1 | C1 | topics | 0 | 5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | name | display | showexpanded | | folder | C1 | Test folder | 0 | 0 | And I am on the "Test folder" "folder activity" page logged in as "teacher1" And I press "Edit" And I press "Create folder" And I set the field "New folder name" to "Test subfolder 1" And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element" And I press "Save changes" @javascript Scenario: Add a folder with two subfolders - view on separate page Given I am on "Course 1" course homepage And I should not see "Test subfolder 1" And I am on the "Test folder" "folder activity" page And I should see "Test subfolder 1" And I press "Edit" And I press "Create folder" And I set the field "New folder name" to "Test subfolder 2" And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element" And I press "Save changes" When I am on "Course 1" course homepage Then I should not see "Test subfolder 2" And I am on the "Test folder" "folder activity" page And I should see "Test subfolder 2" And I am on the "Test folder" "folder activity editing" page And I set the field "Show subfolders expanded" to "1" When I am on "Course 1" course homepage Then I should not see "Test subfolder 2" And I am on the "Test folder" "folder activity" page And I should see "Test subfolder 2" @javascript Scenario: Make the subfolders viewable inline on the course page Given I press "Edit" And I click on "div.fp-filename" "css_element" in the "div.fp-filename-field" "css_element" And I press "Create folder" And I set the field "New folder name" to "Test sub subfolder" And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element" And I press "Save changes" And I navigate to "Settings" in current page administration When I set the field "Display folder contents" to "Inline on a course page" And I press "Save and return to course" Then I should see "Test subfolder 1" And I should not see "Test sub subfolder" And I am on the "Test folder" "folder activity editing" page And I set the field "Show subfolders expanded" to "1" And I press "Save and return to course" Then I should see "Test subfolder 1" And I should see "Test sub subfolder" course_summary_format.feature 0000644 00000002644 15151776367 0012576 0 ustar 00 @core @core_course Feature: Summary text format should be preserved on edit and set by preferred editor format on creation In order to edit the course summary As a course creator The format specified for the summary must be honored Scenario: Preferred editor format should be used for summary field on course creation Given the following "user preferences" exist: | user | preference | value | | admin | htmleditor | textarea | And I log in as "admin" And I go to the courses management page When I click on "Create new course" "link" Then the field "Course summary format" matches value "0" Scenario: Summary format must be preserved on course edit Given the following "user preferences" exist: | user | preference | value | | admin | htmleditor | textarea | And I log in as "admin" And I go to the courses management page And I click on "Create new course" "link" And I set the following fields to these values: | Course full name | C1 | | Course short name | C1 | | Course summary | Course description | # 4 is assumed to be Markdown format. And I set the field with xpath "//select[@name='summary_editor[format]']" to "4" And I press "Save and display" When I click on "Settings" "link" Then the field "Course summary format" matches value "4" activity_chooser.feature 0000644 00000027647 15151776367 0011541 0 ustar 00 @core @core_course @javascript Feature: Display and choose from the available activities in course In order to add activities to a course As a teacher I should be enabled to choose from a list of available activities and also being able to read their summaries. Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher | Teacher | 1 | teacher@example.com | And the following "courses" exist: | fullname | shortname | format | | Course | C | topics | And the following "course enrolments" exist: | user | course | role | | teacher | C | editingteacher | And the following config values are set as admin: | enablemoodlenet | 0 | tool_moodlenet | And I log in as "teacher" And I am on "Course" course homepage with editing mode on Scenario: The available activities are displayed to the teacher in the activity chooser Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" Then I should see "Add an activity or resource" in the ".modal-title" "css_element" And I should see "Assignment" in the ".modal-body" "css_element" Scenario: The teacher can choose to add an activity from the activity items in the activity chooser Given I click on "Add an activity or resource" "button" in the "Topic 4" "section" When I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue" Then I should see "Adding a new Assignment" And I set the following fields to these values: | Assignment name | Test Assignment Topic 4 | And I press "Save and return to course" Then I should see "Test Assignment Topic 4" in the "Topic 4" "section" Scenario: The teacher can choose to add an activity from the activity summary in the activity chooser Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" When I click on "Add a new Assignment" "link" in the "help" "core_course > Activity chooser screen" Then I should see "Adding a new Assignment" Scenario: Show summary Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue" Then I should see "Assignment" in the "help" "core_course > Activity chooser screen" And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." Scenario: Hide summary Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" When I click on "Information about the Assignment activity" "button" in the "modules" "core_course > Activity chooser screen" And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "help" "core_course > Activity chooser screen" And I should see "Back" in the "help" "core_course > Activity chooser screen" When I click on "Back" "button" in the "help" "core_course > Activity chooser screen" Then "modules" "core_course > Activity chooser screen" should be visible And "help" "core_course > Activity chooser screen" should not be visible And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen" And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue" Scenario: View recommended activities When I log out And I log in as "admin" And I am on site homepage And I navigate to "Courses > Activity chooser > Recommended activities" in site administration And I click on ".activity-recommend-checkbox" "css_element" in the "Book" "table_row" # Setup done, lets check it works with a teacher. And I log out And I log in as "teacher" And I am on "Course" course homepage with editing mode on And I open the activity chooser Then I should see "Recommended" in the "Add an activity or resource" "dialogue" And I click on "Recommended" "link" in the "Add an activity or resource" "dialogue" And I should see "Book" in the "recommended" "core_course > Activity chooser tab" Scenario: Favourite a module in the activity chooser Given I open the activity chooser And I should not see "Starred" in the "Add an activity or resource" "dialogue" And I click on "Star Assignment activity" "button" in the "Add an activity or resource" "dialogue" And I should see "Starred" in the "Add an activity or resource" "dialogue" When I click on "Starred" "link" in the "Add an activity or resource" "dialogue" Then I should see "Assignment" in the "favourites" "core_course > Activity chooser tab" And I click on "Information about the Assignment activity" "button" in the "favourites" "core_course > Activity chooser tab" And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." Scenario: Add a favourite module and check it exists when reopening the chooser Given I open the activity chooser And I click on "Star Assignment activity" "button" in the "Add an activity or resource" "dialogue" And I click on "Star Forum activity" "button" in the "Add an activity or resource" "dialogue" And I should see "Starred" in the "Add an activity or resource" "dialogue" And I click on "Close" "button" in the "Add an activity or resource" "dialogue" When I click on "Add an activity or resource" "button" in the "Topic 3" "section" And I click on "Starred" "link" in the "Add an activity or resource" "dialogue" Then I should see "Forum" in the "favourites" "core_course > Activity chooser tab" Scenario: Add a favourite and then remove it whilst checking the tabs work as expected Given I open the activity chooser And I click on "Star Assignment activity" "button" in the "Add an activity or resource" "dialogue" And I click on "Starred" "link" in the "Add an activity or resource" "dialogue" And I click on "Star Assignment activity" "button" in the "Add an activity or resource" "dialogue" Then I should not see "Starred" in the "Add an activity or resource" "dialogue" Scenario: The teacher can search for an activity by it's name Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" When I set the field "search" to "Lesson" Then I should see "1 results found" in the "Add an activity or resource" "dialogue" And I should see "Lesson" in the "Add an activity or resource" "dialogue" Scenario: The teacher can search for an activity by it's description Given I open the activity chooser When I set the field "search" to "The lesson activity module enables a teacher to deliver content" Then I should see "1 results found" in the "Add an activity or resource" "dialogue" And I should see "Lesson" in the "Add an activity or resource" "dialogue" Scenario: Search results are not returned if the search query does not match any activity name or description Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" When I set the field "search" to "Random search query" Then I should see "0 results found" in the "Add an activity or resource" "dialogue" And ".option" "css_element" should not exist in the ".searchresultitemscontainer" "css_element" Scenario: Teacher can return to the default activity chooser state by manually removing the search query Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" And I set the field "search" to "Lesson" And I should see "1 results found" in the "Add an activity or resource" "dialogue" And I should see "Lesson" in the "Add an activity or resource" "dialogue" When I set the field "search" to "" And I should not see "1 results found" in the "Add an activity or resource" "dialogue" Then ".searchresultscontainer" "css_element" should not be visible And ".optionscontainer" "css_element" should exist Scenario: Teacher can not see a "clear" button if a search query is not entered in the activity chooser search bar When I click on "Add an activity or resource" "button" in the "Topic 1" "section" Then "Clear search input" "button" should not be visible Scenario: Teacher can see a "clear" button after entering a search query in the activity chooser search bar Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" When I set the field "search" to "Search query" Then "Clear search input" "button" should not be visible Scenario: Teacher can not see a "clear" button if the search query is removed in the activity chooser search bar Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" And I set the field "search" to "Search query" And "Clear search input" "button" should exist When I set the field "search" to "" # Waiting for the animation to hide the button to finish. And I wait "1" seconds Then "Clear search input" "button" should not be visible Scenario: Teacher can instantly remove the search query from the activity search bar by clicking on the "clear" button Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" And I set the field "search" to "Search query" And I should see "results found" in the "Add an activity or resource" "dialogue" When I click on "Clear search input" "button" Then I should not see "Search query" And ".searchresultscontainer" "css_element" should not be visible And ".optionscontainer" "css_element" should exist Scenario: Teacher gets the base case for the Activity Chooser tab mode Given I click on "Add an activity or resource" "button" in the "Topic 1" "section" And I should see "Activities" in the "Add an activity or resource" "dialogue" When I click on "Activities" "link" in the "Add an activity or resource" "dialogue" Then I should not see "Book" in the "activity" "core_course > Activity chooser tab" And I click on "Resources" "link" in the "Add an activity or resource" "dialogue" And I should not see "Assignment" in the "resources" "core_course > Activity chooser tab" Scenario: Teacher gets the simple case for the Activity Chooser tab mode Given I log out And I log in as "admin" And I am on site homepage When I navigate to "Courses > Activity chooser > Activity chooser settings" in site administration And I select "Starred, All, Recommended" from the "Activity chooser tabs" singleselect And I press "Save changes" And I log out And I log in as "teacher" And I am on "Course" course homepage with editing mode on And I click on "Add an activity or resource" "button" in the "Topic 1" "section" Then I should not see "Activities" in the "Add an activity or resource" "dialogue" And I should not see "Resources" in the "Add an activity or resource" "dialogue" Scenario: Teacher gets the final case for the Activity Chooser tab mode Given I log out And I log in as "admin" And I am on site homepage When I navigate to "Courses > Activity chooser > Activity chooser settings" in site administration And I select "Starred, Activities, Resources, Recommended" from the "Activity chooser tabs" singleselect And I press "Save changes" And I log out And I log in as "teacher" And I am on "Course" course homepage with editing mode on And I click on "Add an activity or resource" "button" in the "Topic 1" "section" Then I should not see "All" in the "Add an activity or resource" "dialogue" And I should see "Activities" in the "Add an activity or resource" "dialogue" And I should see "Resources" in the "Add an activity or resource" "dialogue" keyholder.feature 0000644 00000004521 15151776367 0010133 0 ustar 00 @core @core_course @javascript Feature: Keyholder role is listed as course contact As a student I need to know who the keyholder is to enrol in a course Background: Given I log in as "admin" And I am on site homepage And the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And I navigate to "Users > Permissions > Define roles" in site administration And I click on "Add a new role" "button" And I click on "Continue" "button" And I set the following fields to these values: | Short name | keyholder | | Custom full name | Keyholder | | contextlevel40 | 1 | | contextlevel50 | 1 | | enrol/self:holdkey | 1 | And I click on "Create this role" "button" And I navigate to "Appearance > Courses" in site administration And I click on "Keyholder" "checkbox" And I press "Save changes" And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | keyholder1 | Keyholder | 1 | keyholder1@example.com | | student1 | Student | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | category | | Course 1 | C1 | topics | 0 | 5 | CAT1 | And I add "Self enrolment" enrolment method in "Course 1" with: | Custom instance name | Test student enrolment | | Enrolment key | letmein | And I log out Scenario: Keyholder assigned to a course When I log in as "admin" And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | keyholder1 | C1 | keyholder | And I log out And I log in as "student1" And I am on site homepage And I follow "Course 1" Then I should see "Keyholder 1" Scenario: Keyholder assigned to a category When I log in as "admin" And the following "role assigns" exist: | user | role | contextlevel | reference | | keyholder1 | keyholder | Category | CAT1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And I log out And I log in as "student1" And I am on site homepage And I follow "Course 1" Then I should see "Keyholder 1" category_resort.feature 0000644 00000025660 15151776367 0011367 0 ustar 00 @core @core_course Feature: Test we can resort categories in the management interface. As a moodle admin I need to test we can resort top level categories. I need to test we can resort sub categories. I need to test we can manually sort categories. Scenario Outline: Test bulk sorting all categories. Given the following "categories" exist: | category | name | idnumber | sortorder | | 0 | Social studies | Ext003 | 1 | | 0 | Applied sciences | Sci001 | 2 | | 0 | Extended social studies | Ext002 | 3 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I set the field "menuselectsortby" to "All categories" And I set the field "menuresortcategoriesby" to <sortby> And I press "Sort" # Redirect. And I should see the "Course categories and courses" management page And I should see category listing <cat1> before <cat2> And I should see category listing <cat2> before <cat3> Examples: | sortby | cat1 | cat2 | cat3 | | "Sort by Category name ascending" | "Applied sciences" | "Extended social studies" | "Social studies" | | "Sort by Category name descending" | "Social studies" | "Extended social studies" | "Applied sciences" | | "Sort by Category ID number ascending" | "Extended social studies" | "Social studies" | "Applied sciences" | | "Sort by Category ID number descending" | "Applied sciences" | "Social studies" | "Extended social studies" | Scenario Outline: Test bulk sorting current category. Given the following "categories" exist: | category | name | idnumber | sortorder | | 0 | Test category | Tes001 | 1 | | Tes001 | Social studies | Ext003 | 2 | | Tes001 | Applied sciences | Sci001 | 3 | | Tes001 | Extended social studies | Ext002 | 4 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "Test category" "link" # Redirect. And I should see the "Course categories and courses" management page And I set the field "menuselectsortby" to "This category" And I set the field "menuresortcategoriesby" to <sortby> And I press "Sort" # Redirect. And I should see the "Course categories and courses" management page And I should see category listing <cat1> before <cat2> And I should see category listing <cat2> before <cat3> Examples: | sortby | cat1 | cat2 | cat3 | | "Sort by Category name ascending" | "Applied sciences" | "Extended social studies" | "Social studies" | | "Sort by Category name descending" | "Social studies" | "Extended social studies" | "Applied sciences" | | "Sort by Category ID number ascending" | "Extended social studies" | "Social studies" | "Applied sciences" | | "Sort by Category ID number descending" | "Applied sciences" | "Social studies" | "Extended social studies" | Scenario Outline: Test resorting subcategories. Given the following "categories" exist: | category | name | idnumber | sortorder | | 0 | Master cat | CAT1 | 1 | | CAT1 | Social studies | Ext003 | 1 | | CAT1 | Applied sciences | Sci001 | 2 | | CAT1 | Extended social studies | Ext002 | 3 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "Master cat" "link" # Redirect. And I should see the "Course categories and courses" management page And I click on <sortby> action for "Master cat" in management category listing # Redirect. And I should see the "Course categories and courses" management page And I should see category listing <cat1> before <cat2> And I should see category listing <cat2> before <cat3> Examples: | sortby | cat1 | cat2 | cat3 | | "resortbyname" | "Applied sciences" | "Extended social studies" | "Social studies" | | "resortbynamedesc" | "Social studies" | "Extended social studies" | "Applied sciences" | | "resortbyidnumber" | "Extended social studies" | "Social studies" | "Applied sciences" | | "resortbyidnumberdesc" | "Applied sciences" | "Social studies" | "Extended social studies" | @javascript Scenario Outline: Test resorting subcategories with JS enabled. Given the following "categories" exist: | category | name | idnumber | sortorder | | 0 | Master cat | CAT1 | 1 | | CAT1 | Social studies | Ext003 | 1 | | CAT1 | Applied sciences | Sci001 | 2 | | CAT1 | Extended social studies | Ext002 | 3 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on "Master cat" category in the management category listing And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on <sortby> action for "Master cat" in management category listing And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see category listing <cat1> before <cat2> And I should see category listing <cat2> before <cat3> Examples: | sortby | cat1 | cat2 | cat3 | | "resortbyname" | "Applied sciences" | "Extended social studies" | "Social studies" | | "resortbynamedesc" | "Social studies" | "Extended social studies" | "Applied sciences" | | "resortbyidnumber" | "Extended social studies" | "Social studies" | "Applied sciences" | | "resortbyidnumberdesc" | "Applied sciences" | "Social studies" | "Extended social studies" | # The scenario below this is the same but with JS enabled. Scenario: Test moving categories up and down by one. Given the following "categories" exist: | category | idnumber | name | | 0 | CAT1 | Cat 1 | | 0 | CAT2 | Cat 2 | | CAT1 | CATA | Cat 1a | | CAT1 | CATB | Cat 1b | | CAT1 | CATC | Cat 1c | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface # Redirect. We should a 1, 1a, 1b, 1c, 2. And I should see the "Course categories and courses" management page And I should see category listing "Cat 1" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 2" And I click to move category "CATA" down one # Redirect.We should a 1, 1b, 1a, 1c, 2. And I should see the "Course categories and courses" management page And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 2" And I click to move category "CATC" up one # Redirect. We should a 1, 1b, 1c, 1a, 2. And I should see the "Course categories and courses" management page And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 2" And I click to move category "CATA" down one # Redirect. We should a 1, 1b, 1c, 1a, 2. And I should see the "Course categories and courses" management page And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 2" And I click to move category "CATB" up one # Redirect. We should a 1, 1b, 1c, 1a, 2. And I should see the "Course categories and courses" management page And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 2" And I click to move category "CAT2" up one # Redirect. We should a 2, 1. And I should see the "Course categories and courses" management page And I should see category listing "Cat 2" before "Cat 1" And I click on category "Cat 1" in the management interface # Redirect. We should a 2, 1, 1b, 1c, 1a. And I should see the "Course categories and courses" management page And I should see category listing "Cat 2" before "Cat 1" And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 1a" @javascript @_cross_browser Scenario: Test using AJAX to move categories up and down by one. Given the following "categories" exist: | category | idnumber | name | | 0 | CAT1 | Cat 1 | | 0 | CAT2 | Cat 2 | | CAT1 | CATA | Cat 1a | | CAT1 | CATB | Cat 1b | | CAT1 | CATC | Cat 1c | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see category listing "Cat 1" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 2" And I click to move category "CATA" down one And a new page should not have loaded since I started watching And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 2" And I click to move category "CATC" up one And a new page should not have loaded since I started watching And I should see category listing "Cat 1" before "Cat 1b" And I should see category listing "Cat 1b" before "Cat 1c" And I should see category listing "Cat 1c" before "Cat 1a" And I should see category listing "Cat 1a" before "Cat 2" course_format.feature 0000644 00000010143 15151776367 0011012 0 ustar 00 @core @core_course Feature: Teacher can change the course format In order to change course format As a teacher I should be able to edit a course @javascript Scenario: Teacher can change the course format Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | # Course format is initially set to Topics format And the following "courses" exist: | fullname | shortname | format | startdate | | Course 1 | C1 | topics | ## 1 day ago ## | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | # Confirm that course format is Topics When I am on the "Course 1" course page logged in as teacher1 Then I should see "Topic 1" And I am on the "Course 1" "course editing" page And I expand all fieldsets # Fields that appear for Topics format exist # Also confirm contents of Hidden sections and Course layout select box And I should see "Hidden sections" And the "Hidden sections" select box should contain "Hidden sections are shown as not available" And the "Hidden sections" select box should contain "Hidden sections are completely invisible" # Hidden sections default value is 1 (Hidden sections are completely invisible) And the field "Hidden sections" matches value "1" And I should see "Course layout" And the "Course layout" select box should contain "Show all sections on one page" And the "Course layout" select box should contain "Show one section per page" # Course layout default value is 0 (Show all sections on one page) And the field "Course layout" matches value "0" # Set course format to Single activity format And I set the field "Format" to "Single activity format" And I expand all fieldsets # Confirm that fields that appear for Single activity format appears And I should see "Type of activity" And I set the field "Type of activity" to "Glossary" And I press "Save and display" And I set the field "Name" to "Glossary 1" And I press "Save and display" # Confirm that course page displays single activity of type Glossary And I should see "Browse the glossary using this index" And I should not see "Topic 1" And I am on the "Course 1" "course editing" page And I expand all fieldsets # Set course format to Weekly format And I set the field "Format" to "Weekly format" And I expand all fieldsets # Confirm that fields that appear for Weekly format appears # Also confirm contents of Hidden sections and Course layout select box And I should see "Hidden sections" And the "Hidden sections" select box should contain "Hidden sections are shown as not available" And the "Hidden sections" select box should contain "Hidden sections are completely invisible" # Hidden sections default value is 1 (Hidden sections are completely invisible) And the field "Hidden sections" matches value "1" And I should see "Course layout" And the "Course layout" select box should contain "Show all sections on one page" And the "Course layout" select box should contain "Show one section per page" # Course layout default value is 0 (Show all sections on one page) And the field "Course layout" matches value "0" And I press "Save and display" # Confirm that course page displays weekly sections And I should see "This week" And I should not see "Browse the glossary using this index" And I am on the "Course 1" "course editing" page And I expand all fieldsets # Set course format to Social format And I set the field "Format" to "Social format" # Confirm that fields that appear for Social format appears And I expand all fieldsets And I should see "Number of discussions" And the field "Number of discussions" matches value "10" And I press "Save and display" # Confirm that course page displays a forum And I should see "There are no discussion topics yet in this forum" And I should not see "This week" category_management.feature 0000644 00000055215 15151776367 0012164 0 ustar 00 @core @core_course Feature: Test category management actions As a moodle admin Test we can create a category Test we can create a sub category Test we can edit a category Test we can delete a category Test deleting categories interface when user permissions are restricted Test we can move a category Test we can assign roles within a category Test we can set permissions on a category Test we can manage cohorts within a category Test we can manage filters for a category Scenario: Test editing a category through the management interface. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "edit" action for "Cat 1" in management category listing # Redirect And I should see "Edit category settings" And I should see "Cat 1" And I press "Cancel" # Redirect And I should see the "Course categories and courses" management page And I click on "edit" action for "Cat 1" in management category listing # Redirect And I should see "Edit category settings" And I should see "Cat 1" And I set the following fields to these values: | Category name | Category 1 (edited) | | Category ID number | CAT1e | And I press "Save changes" # Redirect And I should see the "Course categories and courses" management page And I should see "Category 1 (edited)" in the "#category-listing" "css_element" And I should see "Category 1 (edited)" in the "#course-listing h3" "css_element" @javascript Scenario: Test deleting a categories through the management interface. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 3 | 0 | CAT3 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT3 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And I click on "delete" action for "Cat 2" in management category listing # Redirect And I should see "Delete category: Cat 2" And I should see "Contents of Cat 2" And I should see "This category is empty" And I press "Cancel" # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And I click on "delete" action for "Cat 2" in management category listing # Redirect And I should see "Delete category: Cat 2" And I should see "Contents of Cat 2" And I should see "This category is empty" And I press "Delete" # Redirect And I should see "Delete category: Cat 2" And I should see "Deleted course category Cat 2" And I press "Continue" # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should not see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And I click on "delete" action for "Cat 3" in management category listing # Redirect And I should see "Delete category: Cat 3" And I set the following fields to these values: | What to do | Move contents to another category | | Move into | Cat 1 | And I press "Delete" # Redirect And I should see "Delete category: Cat 3" And I should see "Deleted course category Cat 3" And I press "Continue" # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should not see "Cat 2" in the "#category-listing ul" "css_element" And I should not see "Cat 3" in the "#category-listing ul" "css_element" And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" Scenario: Test deleting categories action is not listed when permissions are restricted. Given the following "users" exist: | username | firstname | lastname | | manager | Manager | Manager | And the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | And the following "courses" exist: | category | fullname | shortname | | CAT1 | Course 1 | C1 | And the following "system role assigns" exist: | user | role | contextlevel | | manager | manager | System | And the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:delete | Prevent | manager | Course | C1 | | moodle/course:create | Prevent | manager | System | | When I log in as "manager" And I go to the courses management page Then I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I open the action menu for "Cat 1" in management category listing And "Cat 1" category actions menu should not have "Delete" item Scenario: Test deleting categories interface when course create permission is restricted in system. Given the following "users" exist: | username | firstname | lastname | | manager | Manager | Manager | And the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | And the following "courses" exist: | category | fullname | shortname | | CAT1 | Course 1 | C1 | And the following "system role assigns" exist: | user | role | contextlevel | | manager | manager | System | And the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:delete | Allow | manager | Course | C1 | | moodle/course:create | Prevent | manager | System | | When I log in as "manager" And I go to the courses management page And I open the action menu for "Cat 1" in management category listing Then "Cat 1" category actions menu should have "Delete" item And I click on "delete" action for "Cat 1" in management category listing # Redirect And I should see "Delete category: Cat 1" And I should see "Contents of Cat 1" And I should see "Delete all - cannot be undone" And "What to do" "select" should not exist And "Move into" "select" should not exist And I press "Cancel" Scenario: Test deleting categories interface when course delete permission is restricted for category. Given the following "users" exist: | username | firstname | lastname | | manager | Manager | Manager | And the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | And the following "courses" exist: | category | fullname | shortname | | CAT1 | Course 1 | C1 | And the following "system role assigns" exist: | user | role | contextlevel | | manager | manager | System | And the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:delete | Prevent | manager | Course | C1 | | moodle/course:create | Allow | manager | System | | When I log in as "manager" And I go to the courses management page And I open the action menu for "Cat 1" in management category listing Then "Cat 1" category actions menu should have "Delete" item And I click on "delete" action for "Cat 1" in management category listing # Redirect And I should see "Delete category: Cat 1" And I should see "Contents of Cat 1" And I should see "Move contents to another category" And "What to do" "select" should not exist And "Move into" "select" should exist And the "Move into" select box should contain "Cat 2" And the "Move into" select box should contain "Category 1" And I press "Cancel" @javascript Scenario: Test deleting categories interface when course create permissions are restricted for some categories. Given the following "users" exist: | username | firstname | lastname | | manager | Manager | Manager | And the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | And the following "courses" exist: | category | fullname | shortname | | CAT1 | Course 1 | C1 | And the following "system role assigns" exist: | user | role | contextlevel | | manager | manager | System | And the following "permission overrides" exist: | capability | permission | role | contextlevel | reference | | moodle/course:delete | Allow | manager | Course | C1 | | moodle/course:create | Allow | manager | System | | | moodle/course:create | Prevent | manager | Category | CAT2 | When I log in as "manager" And I go to the courses management page And I open the action menu for "Cat 1" in management category listing Then "Cat 1" category actions menu should have "Delete" item And I click on "delete" action for "Cat 1" in management category listing # Redirect And I should see "Delete category: Cat 1" And I should see "Contents of Cat 1" And "What to do" "select" should exist And I expand the "Move into" autocomplete And "Cat 2" "autocomplete_suggestions" should not exist And "Category 1" "autocomplete_selection" should be visible And I set the field "What to do" to "Delete all - cannot be undone" And "Move into" "select" should not be visible And I press "Cancel" Scenario: Test I can assign roles for a category through the management interface. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "permissions" action for "Cat 1" in management category listing And I select "Assign roles" from the "jump" singleselect # Redirect And I should see "Assign roles in Category: Cat 1" And I should see "Please choose a role to assign" Scenario: Test I can set access permissions for a category through the management interface. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "permissions" action for "Cat 1" in management category listing # Redirect And I should see "Permissions in Category: Cat 1" And I click on "Back to Category: Cat 1" "link" # Redirect And I should see "Cat 1" in the "h1" "css_element" Scenario: Test clicking to manage cohorts for a category through the management interface. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "cohorts" action for "Cat 1" in management category listing # Redirect And I should see "Cohorts" Scenario: Test configuring filters for a category Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "filters" action for "Cat 1" in management category listing # Redirect And I should see "Filter settings in Category: Cat 1" And I click on "Back to Category: Cat 1" "link" # Redirect And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-listing h3" "css_element" @javascript Scenario: Test that I can create a category and view it in the management interface Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "Create new category" "link" in the ".category-listing-actions" "css_element" # Redirect. And I should see "Add new category" And I set the following fields to these values: | Parent category | Top | | Category name | Test category 2 | | Category ID number | TC2 | And I press "Create category" # Redirect And I should see the "Course categories and courses" management page And I should see "Test category 2" in the "#course-listing h3" "css_element" And I should see category listing "Cat 1" before "Test category 2" And I should see "No courses in this category" And I click on "createnewsubcategory" action for "Test category 2" in management category listing # Redirect And I should see "Add new category" And I set the following fields to these values: | Parent category | Top | | Category name | Test category 3 | | Category ID number | TC3 | And I press "Create category" # Redirect And I should see the "Course categories and courses" management page And I should see "Test category 3" in the "#course-listing h3" "css_element" And I should see category listing "Cat 1" before "Test category 2" And I should see "No courses in this category" @javascript Scenario: Test moving a categories through the management interface. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 3 | 0 | CAT3 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And I select category "Cat 2" in the management interface And I select category "Cat 3" in the management interface And I set the field "menumovecategoriesto" to "Cat 1" When I press "bulkmovecategories" # Redirect And I click on category "Cat 1" in the management interface # Redirect Then I should see category "CAT3" as subcategory of "CAT1" in the management interface And I move category "Cat 3" to top level in the management interface # Redirect And I should not see category "CAT3" as subcategory of "CAT1" in the management interface Then I should see category "CAT2" as subcategory of "CAT1" in the management interface @javascript Scenario: Test bulk action is shown only when some category/course is selected Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 3 | 0 | CAT3 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT3 | Course 1 | Course 1 | C1 | | CAT3 | Course 2 | Course 2 | C2 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#category-listing ul" "css_element" And I should see "Cat 2" in the "#category-listing ul" "css_element" And I should see "Cat 3" in the "#category-listing ul" "css_element" And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled When I set the field "selectsortby" to "allcategories" Then the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And the "movecategoriesto" "select" should be disabled And I select category "Cat 2" in the management interface And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I set the field "selectsortby" to "selectedcategories" And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I unselect category "Cat 2" in the management interface And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled And I select category "Cat 3" in the management interface And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I select category "Cat 2" in the management interface And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I unselect category "Cat 2" in the management interface And I unselect category "Cat 3" in the management interface And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled And I click on category "Cat 1" in the management interface # Redirect. And I should see the "Course categories and courses" management page And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled And the "movecoursesto" "select" should be disabled And I click on category "Cat 3" in the management interface #Redirect And I should see the "Course categories and courses" management page And I should see "Course 1" in the "#course-listing ul.course-list" "css_element" And I should see "Course 2" in the "#course-listing ul.course-list" "css_element" And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled And the "movecoursesto" "select" should be disabled And I select course "Course 1" in the management interface And the "movecoursesto" "select" should be enabled And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled And I select course "Course 2" in the management interface And the "movecoursesto" "select" should be enabled And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled And I select category "Cat 3" in the management interface And the "movecoursesto" "select" should be enabled And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I unselect course "Course 2" in the management interface And the "movecoursesto" "select" should be enabled And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I unselect course "Course 1" in the management interface And the "movecoursesto" "select" should be disabled And the "movecategoriesto" "select" should be enabled And the "resortcategoriesby" "select" should be enabled And the "resortcoursesby" "select" should be enabled And I unselect category "Cat 3" in the management interface And the "movecoursesto" "select" should be disabled And the "movecategoriesto" "select" should be disabled And the "resortcategoriesby" "select" should be disabled And the "resortcoursesby" "select" should be disabled Scenario: Test that is not possible to create a course category with a duplicate idnumber Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And I log in as "admin" And I navigate to "Courses > Add a category" in site administration And I set the following fields to these values: | Category name | Test duplicate | | Category ID number | CAT1 | When I press "Create category" Then I should see "ID number is already used for another category" Scenario: Test that is possible to remove an idnumber from a course category Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 || And I log in as "admin" And I go to the courses management page And I should see "CAT1" in the "#category-listing" "css_element" When I click on "edit" action for "Cat 1" in management category listing And I set the following fields to these values: | Category name | Category 1 (edited) | | Category ID number || And I press "Save changes" # Redirect Then I should see "Category 1 (edited)" in the "#category-listing" "css_element" And I should not see "CAT1" in the "#course-listing" "css_element" course_relativedatesmode.feature 0000644 00000004122 15151776367 0013223 0 ustar 00 @core @core_course Feature: Courses can be set up to display dates relative to the user's enrolment date As a course creator In order for me to set up courses I need to be able to set up courses to display dates relative to the user's enrolment date @javascript Scenario: Create a course with relative dates feature disabled Given the following config values are set as admin: | enablecourserelativedates | 0 | And I log in as "admin" And I am on site homepage And I turn editing mode on When I press "Add a new course" And I wait until the page is ready Then I should not see "Relative dates mode" And I should not see "This cannot be changed once the course has been created." @javascript Scenario: Create a course with relative dates feature enabled Given the following config values are set as admin: | enablecourserelativedates | 1 | And I log in as "admin" And I am on site homepage And I turn editing mode on When I press "Add a new course" Then I should see "Relative dates mode" And I should see "Relative dates mode cannot be changed once the course has been created." Scenario: Edit courses with relative dates feature enabled Given the following config values are set as admin: | enablecourserelativedates | 1 | And I log in as "admin" And I create a course with: | Course full name | Course 1 | | Course short name | C1 | | Relative dates mode | Yes | And I create a course with: | Course full name | Course 2 | | Course short name | C2 | | Relative dates mode | No | And I am on "Course 1" course homepage When I navigate to "Settings" in current page administration Then the "Relative dates mode" "select" should be disabled And the field "Relative dates mode" matches value "Yes" And I am on "Course 2" course homepage And I navigate to "Settings" in current page administration And the "Relative dates mode" "select" should be disabled And the field "Relative dates mode" matches value "No" activity_navigation.feature 0000644 00000041124 15151776367 0012220 0 ustar 00 @core @core_course Feature: Activity navigation In order to quickly switch between activities As a user I need to use the activity navigation controls in activities Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | | Course 2 | C2 | topics | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | | student1 | C2 | student | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | assign | Assignment 1 | Test assignment description | C1 | assign1 | 0 | | book | Book 1 | Test book description | C1 | book1 | 0 | | chat | Chat 1 | Test chat description | C1 | chat1 | 0 | | choice | Choice 1 | Test choice description | C1 | choice1 | 1 | | data | Database 1 | Test database description | C1 | data1 | 1 | | feedback | Feedback 1 | Test feedback description | C1 | feedback1 | 1 | | folder | Folder 1 | Test folder description | C1 | folder1 | 2 | | forum | Forum 1 | Test forum description | C1 | forum1 | 2 | | glossary | Glossary 1 | Test glossary description | C1 | glossary1 | 2 | | imscp | Imscp 1 | Test imscp description | C1 | imscp1 | 3 | | label | Label 1 | Test label description | C1 | label1 | 3 | | lesson | Lesson 1 | Test lesson description | C1 | lesson1 | 3 | | lti | Lti 1 | Test lti description | C1 | lti1 | 4 | | page | Page 1 | Test page description | C1 | page1 | 4 | | quiz | Quiz 1 | Test quiz description | C1 | quiz1 | 4 | | resource | Resource 1 | Test resource description | C1 | resource1 | 5 | | scorm | Scorm 1 | Test scorm description | C1 | scorm1 | 5 | | survey | Survey 1 | Test survey description | C1 | survey1 | 5 | | url | Url 1 | Test url description | C1 | url1 | 6 | | wiki | Wiki 1 | Test wiki description | C1 | wiki1 | 6 | | workshop | Workshop 1 | Test workshop description | C1 | workshop1 | 6 | | assign | Assignment 1 | Test assignment description | C2 | assign21 | 0 | And the following config values are set as admin: | allowstealth | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Stealth activity. And I click on "Hide" "link" in the "Forum 1" activity And I click on "Make available" "link" in the "Forum 1" activity # Hidden activity. And I click on "Hide" "link" in the "Glossary 1" activity # Hidden section. And I hide section "5" # Set up book. And I follow "Book 1" And I should see "Add new chapter" And I set the following fields to these values: | Chapter title | Chapter 1 | | Content | In the beginning... blah, blah, blah. | And I press "Save changes" And I log out Scenario: Step through activities in the course as a teacher. Given I log in as "teacher1" And I am on "Course 1" course homepage When I follow "Assignment 1" # The first activity won't have the previous activity link. Then "#prev-activity-link" "css_element" should not exist And I should see "Book 1" in the "#next-activity-link" "css_element" And I follow "Book 1" And I should see "Assignment" in the "#prev-activity-link" "css_element" And I should see "Chat 1" in the "#next-activity-link" "css_element" And I follow "Chat 1" And I should see "Book 1" in the "#prev-activity-link" "css_element" And I should see "Choice 1" in the "#next-activity-link" "css_element" And I follow "Choice 1" And I should see "Chat 1" in the "#prev-activity-link" "css_element" And I should see "Database 1" in the "#next-activity-link" "css_element" And I follow "Database 1" And I should see "Choice 1" in the "#prev-activity-link" "css_element" And I should see "Feedback 1" in the "#next-activity-link" "css_element" And I follow "Feedback 1" And I should see "Database 1" in the "#prev-activity-link" "css_element" # The next link will be Folder 1 because Forum 1 is in stealth mode. And I should see "Folder 1" in the "#next-activity-link" "css_element" And I follow "Folder 1" And I should see "Feedback 1" in the "#prev-activity-link" "css_element" # Hidden activity will have a '(hidden)' text within the activity link. And I should see "Glossary 1 (hidden)" in the "#next-activity-link" "css_element" And I follow "Glossary 1 (hidden)" And I should see "Folder 1" in the "#prev-activity-link" "css_element" And I should see "Imscp 1" in the "#next-activity-link" "css_element" And I follow "Imscp 1" And I should see "Glossary 1" in the "#prev-activity-link" "css_element" # The next link will be Lesson 1 because Label 1 doesn't have a view URL. And I should see "Lesson 1" in the "#next-activity-link" "css_element" And I follow "Lesson 1" And I should see "Imscp 1" in the "#prev-activity-link" "css_element" And I should see "Lti 1" in the "#next-activity-link" "css_element" And I follow "Lti 1" And I should see "Lesson 1" in the "#prev-activity-link" "css_element" And I should see "Page 1" in the "#next-activity-link" "css_element" And I follow "Page 1" And I should see "Lti 1" in the "#prev-activity-link" "css_element" And I should see "Quiz 1" in the "#next-activity-link" "css_element" And I follow "Quiz 1" And I should see "Page 1" in the "#prev-activity-link" "css_element" # Hidden sections will have the activities render with the '(hidden)' text. And I should see "Resource 1 (hidden)" in the "#next-activity-link" "css_element" And I follow "Resource 1 (hidden)" And I should see "Quiz 1" in the "#prev-activity-link" "css_element" And I should see "Scorm 1 (hidden)" in the "#next-activity-link" "css_element" And I follow "Scorm 1 (hidden)" And I should see "Resource 1 (hidden)" in the "#prev-activity-link" "css_element" And I should see "Survey 1 (hidden)" in the "#next-activity-link" "css_element" And I follow "Survey 1 (hidden)" And I should see "Scorm 1 (hidden)" in the "#prev-activity-link" "css_element" And I should see "Url 1" in the "#next-activity-link" "css_element" And I follow "Url 1" And I should see "Survey 1 (hidden)" in the "#prev-activity-link" "css_element" And I should see "Wiki 1" in the "#next-activity-link" "css_element" And I follow "Wiki 1" And I should see "Url 1" in the "#prev-activity-link" "css_element" And I should see "Workshop 1" in the "#next-activity-link" "css_element" And I follow "Workshop 1" And I should see "Wiki 1" in the "#prev-activity-link" "css_element" # The last activity won't have the next activity link. And "#next-activity-link" "css_element" should not exist Scenario: Step through activities in the course as a student. Given I log in as "student1" And I am on "Course 1" course homepage When I follow "Assignment 1" # The first activity won't have the previous activity link. Then "#prev-activity-link" "css_element" should not exist And I should see "Book 1" in the "#next-activity-link" "css_element" And I follow "Book 1" And I should see "Assignment" in the "#prev-activity-link" "css_element" And I should see "Chat 1" in the "#next-activity-link" "css_element" And I follow "Chat 1" And I should see "Book 1" in the "#prev-activity-link" "css_element" And I should see "Choice 1" in the "#next-activity-link" "css_element" And I follow "Choice 1" And I should see "Chat 1" in the "#prev-activity-link" "css_element" And I should see "Database 1" in the "#next-activity-link" "css_element" And I follow "Database 1" And I should see "Choice 1" in the "#prev-activity-link" "css_element" And I should see "Feedback 1" in the "#next-activity-link" "css_element" And I follow "Feedback 1" And I should see "Database 1" in the "#prev-activity-link" "css_element" # The next link will be Folder 1 because Forum 1 is in stealth mode. And I should see "Folder 1" in the "#next-activity-link" "css_element" And I follow "Folder 1" And I should see "Feedback 1" in the "#prev-activity-link" "css_element" # The next link will be Imscp 1 because hidden activities are not shown to students. And I should see "Imscp 1" in the "#next-activity-link" "css_element" And I follow "Imscp 1" And I should see "Folder 1" in the "#prev-activity-link" "css_element" # The next link will be Lesson 1 because Label 1 doesn't have a view URL. And I should see "Lesson 1" in the "#next-activity-link" "css_element" And I follow "Lesson 1" And I should see "Imscp 1" in the "#prev-activity-link" "css_element" And I should see "Lti 1" in the "#next-activity-link" "css_element" And I follow "Lti 1" And I should see "Lesson 1" in the "#prev-activity-link" "css_element" And I should see "Page 1" in the "#next-activity-link" "css_element" And I follow "Page 1" And I should see "Lti 1" in the "#prev-activity-link" "css_element" And I should see "Quiz 1" in the "#next-activity-link" "css_element" And I follow "Quiz 1" And I should see "Page 1" in the "#prev-activity-link" "css_element" # Hidden sections will have the activities hidden so the links won't be available to students. And I should see "Url 1" in the "#next-activity-link" "css_element" And I follow "Url 1" And I should see "Quiz 1" in the "#prev-activity-link" "css_element" And I should see "Wiki 1" in the "#next-activity-link" "css_element" And I follow "Wiki 1" And I should see "Url 1" in the "#prev-activity-link" "css_element" And I should see "Workshop 1" in the "#next-activity-link" "css_element" And I follow "Workshop 1" And I should see "Wiki 1" in the "#prev-activity-link" "css_element" # The last activity won't have the next activity link. And "#next-activity-link" "css_element" should not exist Scenario: Jump to another activity as a teacher Given I log in as "teacher1" When I am on "Course 1" course homepage And I follow "Assignment 1" Then "Jump to..." "field" should exist # The current activity will not be listed. And the "Jump to..." select box should not contain "Assignment 1" # Stealth activities will not be listed. And the "Jump to..." select box should not contain "Forum 1" # Resources without view URL (e.g. labels) will not be listed. And the "Jump to..." select box should not contain "Label 1" # Check drop down menu contents. And the "Jump to..." select box should contain "Book 1" And the "Jump to..." select box should contain "Chat 1" And the "Jump to..." select box should contain "Choice 1" And the "Jump to..." select box should contain "Database 1" And the "Jump to..." select box should contain "Feedback 1" And the "Jump to..." select box should contain "Folder 1" And the "Jump to..." select box should contain "Imscp 1" And the "Jump to..." select box should contain "Lesson 1" And the "Jump to..." select box should contain "Lti 1" And the "Jump to..." select box should contain "Page 1" And the "Jump to..." select box should contain "Quiz 1" And the "Jump to..." select box should contain "Url 1" And the "Jump to..." select box should contain "Wiki 1" And the "Jump to..." select box should contain "Workshop 1" # Hidden activities will be rendered with a '(hidden)' text. And the "Jump to..." select box should contain "Glossary 1 (hidden)" # Activities in hidden sections will be rendered with a '(hidden)' text. And the "Jump to..." select box should contain "Resource 1 (hidden)" And the "Jump to..." select box should contain "Scorm 1 (hidden)" And the "Jump to..." select box should contain "Survey 1 (hidden)" # Jump to an activity somewhere in the middle. When I select "Page 1" from the "Jump to..." singleselect Then I should see "Page 1" And I should see "Lti 1" in the "#prev-activity-link" "css_element" And I should see "Quiz 1" in the "#next-activity-link" "css_element" # Jump to the first activity. And I select "Assignment 1" from the "Jump to..." singleselect And I should see "Book 1" in the "#next-activity-link" "css_element" But "#prev-activity-link" "css_element" should not exist # Jump to the last activity. And I select "Workshop 1" from the "Jump to..." singleselect And I should see "Wiki 1" in the "#prev-activity-link" "css_element" But "#next-activity-link" "css_element" should not exist # Jump to a hidden activity. And I select "Glossary 1" from the "Jump to..." singleselect And I should see "Folder 1" in the "#prev-activity-link" "css_element" And I should see "Imscp 1" in the "#next-activity-link" "css_element" Scenario: Jump to another activity as a student Given I log in as "student1" And I am on "Course 1" course homepage And I follow "Assignment 1" And "Jump to..." "field" should exist # The current activity will not be listed. And the "Jump to..." select box should not contain "Assignment 1" # Stealth activities will not be listed for students. And the "Jump to..." select box should not contain "Forum 1" # Resources without view URL (e.g. labels) will not be listed. And the "Jump to..." select box should not contain "Label 1" # Hidden activities will not be listed for students. And the "Jump to..." select box should not contain "Glossary 1" # Activities in hidden sections will not be listed for students. And the "Jump to..." select box should not contain "Resource 1" And the "Jump to..." select box should not contain "Scorm 1" And the "Jump to..." select box should not contain "Survey 1" # Only activities visible to students will be listed. And the "Jump to..." select box should contain "Book 1" And the "Jump to..." select box should contain "Chat 1" And the "Jump to..." select box should contain "Choice 1" And the "Jump to..." select box should contain "Database 1" And the "Jump to..." select box should contain "Feedback 1" And the "Jump to..." select box should contain "Folder 1" And the "Jump to..." select box should contain "Imscp 1" And the "Jump to..." select box should contain "Lesson 1" And the "Jump to..." select box should contain "Lti 1" And the "Jump to..." select box should contain "Page 1" And the "Jump to..." select box should contain "Quiz 1" And the "Jump to..." select box should contain "Url 1" And the "Jump to..." select box should contain "Wiki 1" And the "Jump to..." select box should contain "Workshop 1" # Jump to an activity somewhere in the middle. When I select "Page 1" from the "Jump to..." singleselect Then I should see "Page 1" And I should see "Lti 1" in the "#prev-activity-link" "css_element" And I should see "Quiz 1" in the "#next-activity-link" "css_element" # Jump to the first activity. And I select "Assignment 1" from the "Jump to..." singleselect And I should see "Book 1" in the "#next-activity-link" "css_element" But "#prev-activity-link" "css_element" should not exist # Jump to the last activity. And I select "Workshop 1" from the "Jump to..." singleselect And I should see "Wiki 1" in the "#prev-activity-link" "css_element" But "#next-activity-link" "css_element" should not exist Scenario: Open an activity in a course that only has a single activity Given I log in as "student1" And I am on "Course 2" course homepage And I follow "Assignment 1" Then "#prev-activity-link" "css_element" should not exist And "#next-activity-link" "css_element" should not exist And "Jump to..." "field" should not exist restrict_available_activities.feature 0000644 00000003573 15151776367 0014236 0 ustar 00 @core @core_course Feature: Restrict activities availability In order to prevent the use of some activities As an admin I need to control which activities can be used in courses Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | format | | Course 1 | C1 | 0 | topics | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | name | | chat | C1 | Test chat name | @javascript Scenario: Activities can be added with the default permissions Given I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I add a "Glossary" to section "1" and I fill the form with: | Name | Test glossary name | | Description | Test glossary description | Then I should see "Test glossary name" And I should see "Test chat name" @javascript @skip_chrome_zerosize Scenario: Activities can not be added when the admin restricts the permissions Given I log in as "admin" And I set the following system permissions of "Teacher" role: | mod/chat:addinstance | Prohibit | And I am on the "Course 1" "permissions" page And I override the system permissions of "Teacher" role with: | mod/glossary:addinstance | Prohibit | And I log out And I log in as "teacher1" When I am on "Course 1" course homepage with editing mode on And I press "Add an activity or resource" Then "Add a new Chat" "link" should not exist in the "Add an activity or resource" "dialogue" Then "Add a new Glossary" "link" should not exist in the "Add an activity or resource" "dialogue" customfields_locked.feature 0000644 00000005023 15151776367 0012165 0 ustar 00 @core @core_course @core_customfield @javascript Feature: Fields locked control who is able to edit it In order to display custom fields on course listing As a manager I can change the visibility of the fields Background: Given the following "custom field categories" exist: | name | component | area | itemid | | Category for test | core_course | course | 0 | Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | Scenario: Editing locked and not locked custom fields When I log in as "admin" And I navigate to "Courses > Course custom fields" in site administration And I click on "Add a new custom field" "link" And I click on "Short text" "link" And I set the following fields to these values: | Name | Test field1 | | Short name | testfield1 | | Locked | No | And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" And I click on "Add a new custom field" "link" And I click on "Short text" "link" And I set the following fields to these values: | Name | Test field2 | | Short name | testfield2 | | Locked | Yes | And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I set the following fields to these values: | Test field1 | testcontent1 | | Test field2 | testcontent2 | And I press "Save and display" And I am on site homepage Then I should see "Test field1: testcontent1" And I should see "Test field2: testcontent2" And I log out And I log in as "teacher1" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration And I expand all fieldsets And the field "Test field1" matches value "testcontent1" And I should not see "Test field2" And I press "Save and display" And I am on site homepage And I should see "Test field1: testcontent1" And I should see "Test field2: testcontent2" activities_edit_completion.feature 0000644 00000004657 15151776367 0013561 0 ustar 00 @core @core_course Feature: Edit completion settings of an activity In order to edit completion settings without accidentally breaking user data As a teacher I need to edit the activity and use the unlock button if required Background: Given the following "courses" exist: | fullname | shortname | enablecompletion | | Course 1 | C1 | 1 | And the following "activities" exist: | activity | course | idnumber | intro | name | completion | completionview | | page | C1 | p1 | x | TestPage | 2 | 1 | Scenario: Completion is not locked when the activity has not yet been viewed Given I am on the TestPage "Page Activity editing" page logged in as admin When I expand all fieldsets Then I should see "Completion tracking" And I should not see "Completion options locked" Scenario: Completion is locked after the activity has been viewed Given I am on the TestPage "Page Activity" page logged in as admin When I am on the TestPage "Page Activity editing" page And I expand all fieldsets Then I should see "Completion options locked" @javascript Scenario: Pressing the unlock button allows the user to edit completion settings Given I am on the TestPage "Page Activity" page logged in as admin When I am on the TestPage "Page Activity editing" page And I expand all fieldsets And I press "Unlock completion settings" And I expand all fieldsets Then I should see "Completion options unlocked" And I set the field "Completion tracking" to "Students can manually mark the activity as completed" And I press "Save and display" And I navigate to "Settings" in current page administration And I expand all fieldsets Then the field "Completion tracking" matches value "Students can manually mark the activity as completed" @javascript Scenario: Even when completion is locked, the user can still set the date Given I am on the TestPage "Page Activity" page logged in as admin And I am on the TestPage "Page Activity editing" page And I expand all fieldsets When I click on "id_completionexpected_enabled" "checkbox" And I set the field "id_completionexpected_year" to "2013" And I press "Save and display" And I navigate to "Settings" in current page administration And I expand all fieldsets Then the field "id_completionexpected_year" matches value "2013" max_number_sections.feature 0000644 00000003572 15151776367 0012216 0 ustar 00 @core @core_course Feature: The maximum number of weeks/topics in a course can be configured In order to set boundaries to courses size As a manager I need to limit the number of weeks/topics a course can have Background: Given the following "users" exist: | username | firstname | lastname | email | | manager1 | Manager | 1 | manager1@example.com | And the following "system role assigns" exist: | user | course | role | | manager1 | Acceptance test site | manager | And I log in as "admin" And I navigate to "Courses > Course default settings" in site administration @javascript Scenario: The number of sections can be increased and the limits are applied to courses Given I set the field "Maximum number of sections" to "100" When I press "Save changes" Then the field "Maximum number of sections" matches value "100" And the "Number of sections" select box should contain "100" And I log out And I log in as "manager1" And I create a course with: | Course full name | New course fullname | | Course short name | New course shortname | | Number of sections | 90 | | Format | Topics format | And I should see "Topic 90" @javascript Scenario: The number of sections can be reduced to 0 and the limits are applied to courses Given I set the field "Maximum number of sections" to "0" When I press "Save changes" Then the field "Maximum number of sections" matches value "0" And the "Number of sections" select box should contain "0" And the "Number of sections" select box should not contain "52" And I log out And I log in as "manager1" And I create a course with: | Course full name | New course fullname | | Course short name | New course shortname | | Number of sections | 0 | | Format | Topics format | And I should not see "Topic 1" section_highlighting.feature 0000644 00000003434 15151776367 0012340 0 ustar 00 @core @core_course @_cross_browser @javascript Feature: Topic's course sections highlighting In order to highlight parts of the course to students As a teacher I need to highlight one specific section @javascript Scenario Outline: Highlight a topic's course section with course paged mode and without it Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | | Course 1 | C1 | topics | <coursedisplay> | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I turn section "1" highlighting on Then section "1" should be highlighted And I turn section "2" highlighting on And section "2" should be highlighted And section "1" should not be highlighted And I am on "Course 1" course homepage And section "2" should be highlighted And section "1" should not be highlighted And I turn section "2" highlighting off And I wait until the page is ready And section "2" should not be highlighted And I reload the page And section "2" should not be highlighted And I am on "Course 1" course homepage And section "2" should not be highlighted And I log out And I log in as "student1" And I am on "Course 1" course homepage And section "1" should not be highlighted And section "2" should not be highlighted Examples: | coursedisplay | | 0 | | 1 | course_category_management_listing.feature 0000644 00000126662 15151776367 0015302 0 ustar 00 @core @core_course Feature: Course category management interface performs as expected In order to test JS enhanced display of categories and subcategories. As a moodle admin I need to expand and collapse categories. @javascript Scenario Outline: Test general look of management interface Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And I log in as "admin" And I go to the courses management page And I select "<selected>" from the "Viewing" singleselect And I should see "<heading>" in the "h2" "css_element" And I should see "<selected>" in the "Viewing" "select" And I should see "<pagecontent>" in the "#page-content" "css_element" And I should see the "<selected>" management page Examples: | heading | selected | pagecontent | | Manage course categories and courses | Course categories and courses | Course categories | | Manage course categories | Course categories | Course categories | | Manage courses | Courses | Cat 1 | @javascript Scenario: Test view mode functionality Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | fullname | shortname | category | format | | Course 1 | C1 | CAT1 | topics | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Course categories" in the "#category-listing h3" "css_element" And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Course categories" in the "Viewing" "select" And the field "jump" matches value "Course categories and courses" And I start watching to see if a new page loads Then I should see "Course categories and courses" in the "Viewing" "select" And I should see "Course categories" in the "Viewing" "select" And I should see "Courses" in the "Viewing" "select" And I should see "Category 1" in the "#course-listing h3" "css_element" And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "No courses in this category" in the "#course-listing" "css_element" And I click on category "Cat 1" in the management interface And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Course categories" in the "#category-listing h3" "css_element" And I should see "Cat 1" in the "#course-listing h3" "css_element" And I should see "Cat 1" in the "#category-listing" "css_element" And I should see "Course 1" in the "#course-listing" "css_element" Then I should see "Courses" in the "Viewing" "select" And I select "Courses" from the "jump" singleselect And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Courses" management page And I should see "Cat 1" in the "#course-listing h3" "css_element" And I should see "Course 1" in the "#course-listing" "css_element" And I click on course "Course 1" in the management interface And a new page should have loaded since I started watching And I should see the "Courses" management page with a course selected And I should see "Cat 1" in the "#course-listing h3" "css_element" And I should see "Course 1" in the "#course-listing" "css_element" And I should see "Course 1" in the "#course-detail h3" "css_element" And I should see "C1" in the "#course-detail .shortname" "css_element" And I should see "Course 1" in the "#course-detail .fullname" "css_element" And I should see "Topics" in the "#course-detail .format" "css_element" And I should see "Cat 1" in the "#course-detail .category" "css_element" Scenario: Test displaying of sub categories Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 1-1 | CAT1 | CAT3 | | Cat 1-2 | CAT1 | CAT4 | | Cat 1-1-1 | CAT3 | CAT5 | | Cat 1-1-2 | CAT3 | CAT6 | | Cat 2-1 | CAT2 | CAT7 | | Cat 2-1-1 | CAT7 | CAT8 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | CAT1 | | Course 2 | C2 | CAT1 | | Course 3 | C3 | CAT3 | | Course 4 | C4 | CAT3 | | Course 5 | C5 | CAT5 | | Course 6 | C6 | CAT5 | | Course 7 | C7 | CAT8 | | Course 8 | C8 | CAT8 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click on "Cat 1" "link" # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click on "Cat 1-1" "link" # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click on "Cat 2" "link" # Redirect. And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" # This is similar to the above scenario except here we are going to use AJAX # to load the categories. @javascript @_cross_browser Scenario: Test AJAX loading of sub categories Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 1-1 | CAT1 | CAT3 | | Cat 1-2 | CAT1 | CAT4 | | Cat 1-1-1 | CAT3 | CAT5 | | Cat 1-1-2 | CAT3 | CAT6 | | Cat 2-1 | CAT2 | CAT7 | | Cat 2-1-1 | CAT7 | CAT8 | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | CAT1 | | Course 2 | C2 | CAT1 | | Course 3 | C3 | CAT3 | | Course 4 | C4 | CAT3 | | Course 5 | C5 | CAT5 | | Course 6 | C6 | CAT5 | | Course 7 | C7 | CAT8 | | Course 8 | C8 | CAT8 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT1" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT3" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT2" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT7" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT1" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT1" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" @javascript Scenario Outline: Top level categories are displayed correctly when resorted Given the following "categories" exist: | category | name | idnumber | sortorder | | 0 | Social studies | Ext003 | 1 | | 0 | Applied sciences | Sci001 | 2 | | 0 | Extended social studies | Ext002 | 3 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I set the field "menuselectsortby" to "All categories" And I set the field "menuresortcategoriesby" to <sortby> And I press "Sort" And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see category listing <cat1> before <cat2> And I should see category listing <cat2> before <cat3> Examples: | sortby | cat1 | cat2 | cat3 | | "Sort by Category name ascending" | "Applied sciences" | "Extended social studies" | "Social studies" | | "Sort by Category name descending" | "Social studies" | "Extended social studies" | "Applied sciences" | | "Sort by Category ID number ascending" | "Extended social studies" | "Social studies" | "Applied sciences" | | "Sort by Category ID number descending" | "Applied sciences" | "Social studies" | "Extended social studies" | @javascript Scenario Outline: Sub categories are displayed correctly when resorted Given the following "categories" exist: | category | name | idnumber | sortorder | | 0 | Master cat | CAT1 | 1 | | CAT1 | Social studies | Ext003 | 1 | | CAT1 | Applied sciences | Sci001 | 2 | | CAT1 | Extended social studies | Ext002 | 3 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on "Master cat" category in the management category listing And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on <sortby> action for "Master cat" in management category listing And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see category listing <cat1> before <cat2> And I should see category listing <cat2> before <cat3> Examples: | sortby | cat1 | cat2 | cat3 | | "resortbyname" | "Applied sciences" | "Extended social studies" | "Social studies" | | "resortbynamedesc" | "Social studies" | "Extended social studies" | "Applied sciences" | | "resortbyidnumber" | "Extended social studies" | "Social studies" | "Applied sciences" | | "resortbyidnumberdesc" | "Applied sciences" | "Social studies" | "Extended social studies" | @javascript Scenario Outline: Test courses are displayed correctly after being resorted. Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | sortorder | timecreated | | CAT1 | Social studies | Senior school | Ext003 | 1 | 1000000001 | | CAT1 | Applied sciences | Middle school | Sci001 | 2 | 1000000002 | | CAT1 | Extended social studies | Junior school | Ext002 | 3 | 1000000003 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on "Cat 1" "link" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I open the action menu in ".course-listing-actions" "css_element" And I should see "Sort by Course full name ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course full name descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course short name ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course short name descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course ID number ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course ID number descending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course time created ascending" in the ".course-listing-actions" "css_element" And I should see "Sort by Course time created descending" in the ".course-listing-actions" "css_element" And I click on <sortby> "link" in the ".course-listing-actions" "css_element" And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see course listing <course1> before <course2> And I should see course listing <course2> before <course3> Examples: | sortby | course1 | course2 | course3 | | "Sort by Course full name ascending" | "Applied sciences" | "Extended social studies" | "Social studies" | | "Sort by Course full name descending" | "Social studies" | "Extended social studies" | "Applied sciences" | | "Sort by Course short name ascending" | "Extended social studies" | "Applied sciences" | "Social studies" | | "Sort by Course short name descending" | "Social studies" | "Applied sciences" | "Extended social studies" | | "Sort by Course ID number ascending" | "Extended social studies" | "Social studies" | "Applied sciences" | | "Sort by Course ID number descending" | "Applied sciences" | "Social studies" | "Extended social studies" | | "Sort by Course time created ascending" | "Social studies" | "Applied sciences" | "Extended social studies" | | "Sort by Course time created descending" | "Extended social studies" | "Applied sciences" | "Social studies" | @javascript Scenario: Test course pagination Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | | CAT1 | Course 4 | Course 4 | C4 | | CAT1 | Course 5 | Course 5 | C5 | | CAT1 | Course 6 | Course 6 | C6 | | CAT1 | Course 7 | Course 7 | C7 | | CAT1 | Course 8 | Course 8 | C8 | | CAT1 | Course 9 | Course 9 | C9 | | CAT1 | Course 10 | Course 10 | C10 | | CAT1 | Course 11 | Course 11 | C11 | | CAT1 | Course 12 | Course 12 | C12 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on "Cat 1" "link" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I open the action menu in ".course-listing-actions" "css_element" And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see "Per page: 20" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 2" before "Course 3" And I should see course listing "Course 3" before "Course 4" And I should see course listing "Course 4" before "Course 5" And I should see course listing "Course 5" before "Course 6" And I should see course listing "Course 6" before "Course 7" And I should see course listing "Course 7" before "Course 8" And I should see course listing "Course 8" before "Course 9" And I should see course listing "Course 9" before "Course 10" And I should see course listing "Course 10" before "Course 11" And I should see course listing "Course 11" before "Course 12" And "#course-listing .pagination" "css_element" should not exist And I open the action menu in ".courses-per-page" "css_element" And I should see "5" in the ".courses-per-page" "css_element" And I should see "10" in the ".courses-per-page" "css_element" And I should see "20" in the ".courses-per-page" "css_element" And I should see "All" in the ".courses-per-page" "css_element" And I click on "5" "link" in the ".courses-per-page" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 2" before "Course 3" And I should see course listing "Course 3" before "Course 4" And I should see course listing "Course 4" before "Course 5" And I should not see "Course 6" And I should not see "Course 7" And I should not see "Course 8" And I should not see "Course 9" And I should not see "Course 10" And I should not see "Course 11" And I should not see "Course 12" And "#course-listing .pagination" "css_element" should exist And I should see "Showing courses 1 to 5 of 12 courses" And I should not see "First" in the "#course-listing .pagination" "css_element" And I should not see "Prev" in the "#course-listing .pagination" "css_element" And I should see "1" in the "#course-listing .pagination" "css_element" And I should see "2" in the "#course-listing .pagination" "css_element" And I should see "3" in the "#course-listing .pagination" "css_element" And I should see "Next" in the "#course-listing .pagination" "css_element" And I click on "2" "link" in the "#course-listing .pagination" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should see "Course 10" in the "#course-listing" "css_element" And I should not see "Course 2" in the "#course-listing" "css_element" And I should not see "Course 3" in the "#course-listing" "css_element" And I should not see "Course 4" in the "#course-listing" "css_element" And I should not see "Course 5" in the "#course-listing" "css_element" And I should see course listing "Course 6" before "Course 7" And I should see course listing "Course 7" before "Course 8" And I should see course listing "Course 8" before "Course 9" And I should see course listing "Course 9" before "Course 10" And I should not see "Course 11" And I should not see "Course 12" And "#course-listing .pagination" "css_element" should exist And I should see "Showing courses 6 to 10 of 12 courses" And I should see "Prev" in the "#course-listing .pagination" "css_element" And I should see "1" in the "#course-listing .pagination" "css_element" And I should see "2" in the "#course-listing .pagination" "css_element" And I should see "3" in the "#course-listing .pagination" "css_element" And I should see "Next" in the "#course-listing .pagination" "css_element" And I click on "Next" "link" in the "#course-listing .pagination" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should see "Course 11" And I should not see "Course 2" in the "#course-listing" "css_element" And I should not see "Course 3" in the "#course-listing" "css_element" And I should not see "Course 4" in the "#course-listing" "css_element" And I should not see "Course 5" in the "#course-listing" "css_element" And I should not see "Course 6" in the "#course-listing" "css_element" And I should not see "Course 7" in the "#course-listing" "css_element" And I should not see "Course 8" in the "#course-listing" "css_element" And I should not see "Course 9" in the "#course-listing" "css_element" And I should not see "Course 10" in the "#course-listing" "css_element" And I should see course listing "Course 11" before "Course 12" And "#course-listing .pagination" "css_element" should exist And I should see "Showing courses 11 to 12 of 12 courses" And I should see "Prev" in the "#course-listing .pagination" "css_element" And I should see "1" in the "#course-listing .pagination" "css_element" And I should see "2" in the "#course-listing .pagination" "css_element" And I should see "3" in the "#course-listing .pagination" "css_element" And I should not see "Next" in the "#course-listing .pagination" "css_element" And I click on "Prev" "link" in the "#course-listing .pagination" "css_element" And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should see "Course 10" in the "#course-listing" "css_element" And I should not see "Course 2" in the "#course-listing" "css_element" And I should not see "Course 3" in the "#course-listing" "css_element" And I should not see "Course 4" in the "#course-listing" "css_element" And I should not see "Course 5" in the "#course-listing" "css_element" And I should see course listing "Course 6" before "Course 7" And I should see course listing "Course 7" before "Course 8" And I should see course listing "Course 8" before "Course 9" And I should see course listing "Course 9" before "Course 10" And I should not see "Course 11" And I should not see "Course 12" And "#course-listing .pagination" "css_element" should exist And I should see "Showing courses 6 to 10 of 12 courses" And I should see "Prev" in the "#course-listing .pagination" "css_element" And I should see "1" in the "#course-listing .pagination" "css_element" And I should see "2" in the "#course-listing .pagination" "css_element" And I should see "3" in the "#course-listing .pagination" "css_element" And I should see "Next" in the "#course-listing .pagination" "css_element" Scenario: Test pagination is only shown when required Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | | CAT1 | Course 4 | Course 4 | C4 | | CAT1 | Course 5 | Course 5 | C5 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on "Cat 1" "link" # Redirect. And I should see the "Course categories and courses" management page And I open the action menu in ".course-listing-actions" "css_element" And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element" # Redirect. And I should see "Per page: 20" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 2" before "Course 3" And I should see course listing "Course 3" before "Course 4" And I should see course listing "Course 4" before "Course 5" And "#course-listing .pagination" "css_element" should not exist And I click on "5" "link" in the ".course-listing-actions" "css_element" # Redirect And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 2" before "Course 3" And I should see course listing "Course 3" before "Course 4" And I should see course listing "Course 4" before "Course 5" And "#course-listing .pagination" "css_element" should not exist # We need at least 30 courses for this next test. @javascript Scenario: Test many course pagination Given the following "categories" exist: | name | category 0| idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | | CAT1 | Course 2 | Course 2 | C2 | | CAT1 | Course 3 | Course 3 | C3 | | CAT1 | Course 4 | Course 4 | C4 | | CAT1 | Course 5 | Course 5 | C5 | | CAT1 | Course 6 | Course 6 | C6 | | CAT1 | Course 7 | Course 7 | C7 | | CAT1 | Course 8 | Course 8 | C8 | | CAT1 | Course 9 | Course 9 | C9 | | CAT1 | Course 10 | Course 10 | C10 | | CAT1 | Course 11 | Course 11 | C11 | | CAT1 | Course 12 | Course 12 | C12 | | CAT1 | Course 13 | Course 13 | C13 | | CAT1 | Course 14 | Course 14 | C14 | | CAT1 | Course 15 | Course 15 | C15 | | CAT1 | Course 16 | Course 16 | C16 | | CAT1 | Course 17 | Course 17 | C17 | | CAT1 | Course 18 | Course 18 | C18 | | CAT1 | Course 19 | Course 19 | C19 | | CAT1 | Course 20 | Course 20 | C20 | | CAT1 | Course 21 | Course 21 | C21 | | CAT1 | Course 22 | Course 22 | C22 | | CAT1 | Course 23 | Course 23 | C23 | | CAT1 | Course 24 | Course 24 | C24 | | CAT1 | Course 25 | Course 25 | C25 | | CAT1 | Course 26 | Course 26 | C26 | | CAT1 | Course 27 | Course 27 | C27 | | CAT1 | Course 28 | Course 28 | C28 | | CAT1 | Course 29 | Course 29 | C29 | | CAT1 | Course 30 | Course 30 | C30 | | CAT1 | Course 31 | Course 31 | C31 | | CAT1 | Course 32 | Course 32 | C32 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I click on "Cat 1" "link" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I open the action menu in ".course-listing-actions" "css_element" And I click on "Sort by Course ID number ascending" "link" in the ".course-listing-actions" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Per page: 20" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 19" before "Course 20" And I should not see "Course 21" And I should see "Showing courses 1 to 20 of 32 courses" And I open the action menu in ".courses-per-page" "css_element" And I click on "100" "link" in the ".courses-per-page" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Per page: 100" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 19" before "Course 20" And I should see course listing "Course 21" before "Course 22" And I should see course listing "Course 31" before "Course 32" And "#course-listing .pagination" "css_element" should not exist And I open the action menu in ".courses-per-page" "css_element" And I click on "5" "link" in the ".courses-per-page" "css_element" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should see course listing "Course 1" before "Course 2" And I should see course listing "Course 4" before "Course 5" And I should not see "Course 6" And I should see "Showing courses 1 to 5 of 32 courses" And I should not see "Prev" in the "#course-listing .pagination" "css_element" And I should see "Next" in the "#course-listing .pagination" "css_element" And I click on "4" "link" in the "#course-listing .pagination" "css_element" And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see "Per page: 5" in the ".course-listing-actions" "css_element" And I should not see "Course 15" And I should see course listing "Course 16" before "Course 17" And I should see course listing "Course 17" before "Course 18" And I should see course listing "Course 18" before "Course 19" And I should see course listing "Course 19" before "Course 20" And I should not see "Course 21" And I should see "Showing courses 16 to 20 of 32 courses" Scenario: Test clicking to edit a course. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | category | fullname | shortname | idnumber | | CAT1 | Course 1 | Course 1 | C1 | And I log in as "admin" And I go to the courses management page And I should see the "Course categories and courses" management page And I click on category "Cat 1" in the management interface And I click on "edit" action for "Course 1" in management course listing # Redirect And I should see "Edit course settings" And I should see "Course 1" @javascript Scenario: Test AJAX expanded categories stay open. Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | | Cat 2 | 0 | CAT2 | | Cat 1-1 | CAT1 | CAT3 | | Cat 1-2 | CAT1 | CAT4 | | Cat 1-1-1 | CAT3 | CAT5 | | Cat 1-1-2 | CAT3 | CAT6 | | Cat 2-1 | CAT2 | CAT7 | | Cat 2-1-1 | CAT7 | CAT8 | | Cat 2-1-1-1 | CAT8 | CAT10 | | Cat 2-1-2 | CAT7 | CAT9 | | Cat 2-1-2-1 | CAT9 | CAT11 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT2" in the management interface And a new page should not have loaded since I started watching And I click to expand category "CAT7" in the management interface And a new page should not have loaded since I started watching And I click to expand category "CAT9" in the management interface And a new page should not have loaded since I started watching And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should not see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-2-1" in the "#course-category-listings ul" "css_element" And I click on "Cat 1" category in the management category listing And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-2-1" in the "#course-category-listings ul" "css_element" And I click on "resortbyidnumber" action for "Cat 1" in management category listing And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see "Cat 1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 1-2" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-2" in the "#course-category-listings ul" "css_element" And I should not see "Cat 2-1-1-1" in the "#course-category-listings ul" "css_element" And I should see "Cat 2-1-2-1" in the "#course-category-listings ul" "css_element" @javascript Scenario: Test category expansion after deletion Given the following "categories" exist: | name | category | idnumber | | Cat A (1) | 0 | CAT1 | | Cat B (2) | 0 | CAT2 | | Cat C (1-1) | CAT1 | CAT3 | | Cat D (2-1) | CAT2 | CAT4 | | Cat E (2-1-1) | CAT4 | CAT5 | And I log in as "admin" And I go to the courses management page And I start watching to see if a new page loads And I should see the "Course categories and courses" management page And I should see "Cat A (1)" in the "#course-category-listings ul" "css_element" And I should see "Cat B (2)" in the "#course-category-listings ul" "css_element" And I should not see "Cat C (1-1)" in the "#course-category-listings ul" "css_element" And I should not see "Cat D (2-1)" in the "#course-category-listings ul" "css_element" And I should not see "Cat E (2-1-1)" in the "#course-category-listings ul" "css_element" And I click to expand category "CAT1" in the management interface And I should see "Cat C (1-1)" in the "#course-category-listings ul" "css_element" And a new page should not have loaded since I started watching And I click to expand category "CAT2" in the management interface And I should see "Cat D (2-1)" in the "#course-category-listings ul" "css_element" And a new page should not have loaded since I started watching And I click to expand category "CAT4" in the management interface And I should see "Cat E (2-1-1)" in the "#course-category-listings ul" "css_element" And a new page should not have loaded since I started watching And I click on "delete" action for "Cat B (2)" in management category listing And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see "Delete category: Cat B (2)" And I should see "Contents of Cat B (2)" And I press "Delete" And a new page should have loaded since I started watching And I start watching to see if a new page loads And I should see "Delete category: Cat B (2)" And I should see "Deleted course category Cat B (2)" And I press "Continue" And a new page should have loaded since I started watching And I should see the "Course categories and courses" management page And I should see "Cat A (1)" in the "#course-category-listings ul" "css_element" And I should not see "Cat B (2)" in the "#course-category-listings ul" "css_element" general_section.feature 0000644 00000003236 15151776370 0011302 0 ustar 00 @format @format_topics Feature: General section does not show in navigation when empty In order to keep my navigation links relevant As a teacher The general section links should not appear in the navigation when the section is empty Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | | Course 1 | C1 | topics | 0 | 5 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | forum | Test forum name | Test forum name description | C1 | forum1 | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Navigation" block if not present Scenario: General section is visible in navigation when it is not empty When I move "Test forum name" activity to section "0" And I am on "Course 1" course homepage Then I should see "General" in the "Navigation" "block" Scenario: General section is not visible in navigation when it is empty When I move "Test forum name" activity to section "3" And I am on "Course 1" course homepage Then I should not see "General" in the "Navigation" "block" force_group_mode.feature 0000644 00000004147 15151776370 0011461 0 ustar 00 @core @core_course @_cross_browser Feature: Force group mode in a course In order to use the same group mode all over the course As a teacher I need to force the group mode of all course's activities Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | category | | Course 1 | C1 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | course | name | | chat | C1 | Chat room | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I navigate to "Settings" in current page administration @javascript Scenario: Forced group mode using separate groups Given I set the following fields to these values: | Group mode | Separate groups | | Force group mode | Yes | When I press "Save and display" Then "//a/child::img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exist And "//img[contains(@alt, 'Separate groups (forced mode)')]" "xpath_element" should not exist @javascript Scenario: Forced group mode using visible groups Given I set the following fields to these values: | Group mode | Visible groups | | Force group mode | Yes | And I press "Save and display" Then "//a/child::img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exist And "//img[contains(@alt, 'Visible groups (forced mode)')]" "xpath_element" should not exist @javascript Scenario: Forced group mode without groups Given I set the following fields to these values: | Group mode | No groups | | Force group mode | Yes | And I press "Save and display" Then "//a/child::img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exist And "//img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exist course_controls.feature 0000644 00000027775 15151776370 0011402 0 ustar 00 @core @core_course Feature: Course activity controls works as expected In order to manage my course's activities As a teacher I need to edit, hide and show activities inside course sections # The difference between these two scenario outlines is that one is with # JS enabled and the other one with JS disabled; we can not use Background # sections when using Scenario Outlines because of Behat framework restrictions. # We are testing: # * Javascript on and off # * Topics and weeks course formats # * Course controls without paged mode # * Course controls with paged mode in the course home page # * Course controls with paged mode in a section's page @javascript @_cross_browser Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | startdate | | Course 1 | C1 | <courseformat> | <coursedisplay> | 5 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I click on <targetpage> "link" in the "region-main" "region" And I add the "Recent activity" block And I open the action menu in "Recent activity" "block" And I click on "Delete Recent activity block" "link" And I click on "Delete" "button" in the "Delete block?" "dialogue" And <belowpage> "section" <should_see_other_sections> exist And I add a "Forum" to section "1" and I fill the form with: | Forum name | Test forum name 1 | | Description | Test forum description 1 | And I add a "Forum" to section "1" and I fill the form with: | Forum name | Test forum name 2 | | Description | Test forum description 2 | And <belowpage> "section" <should_see_other_sections> exist And I open "Test forum name 1" actions menu And I click on "Edit settings" "link" in the "Test forum name 1" activity And I should see "Updating Forum" And I should see "Display description on course page" And I set the following fields to these values: | Forum name | Just to check that I can edit the name | | Description | Just to check that I can edit the description | | Display description on course page | 1 | And I click on "Cancel" "button" And <belowpage> "section" <should_see_other_sections> exist And I open "Test forum name 1" actions menu And I click on "Hide" "link" in the "Test forum name 1" activity And <belowpage> "section" <should_see_other_sections> exist And I delete "Test forum name 1" activity And I should not see "Test forum name 1" in the "region-main" "region" And I duplicate "Test forum name 2" activity editing the new copy with: | Forum name | Edited test forum name 2 | And <belowpage> "section" <should_see_other_sections> exist And I should see "Test forum name 2" And I should see "Edited test forum name 2" And I hide section "1" And <belowpage> "section" <should_see_other_sections> exist And section "1" should be hidden And all activities in section "1" should be hidden And I show section "1" And <belowpage> "section" <should_see_other_sections> exist And section "1" should be visible And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Section links" block And <belowpage> "section" <should_see_other_sections> exist And I should see "1 2 3 4 5" in the "Section links" "block" And I click on "2" "link" in the "Section links" "block" And I <should_see_other_sections_following_block_sections_links> see "Test forum name 2" Examples: | courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage | | topics | 0 | "General" | should | should | "Topic 2" | | topics | 1 | "Topic 1" | should not | should not | "Topic 2" | | topics | 1 | "General" | should | should not | "Topic 2" | | weeks | 0 | "General" | should | should | "8 January - 14 January" | | weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" | | weeks | 1 | "General" | should | should not | "8 January - 14 January" | Scenario Outline: General activities course controls using topics and weeks formats, and paged mode and not paged mode works as expected without javascript Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | startdate | | Course 1 | C1 | <courseformat> | <coursedisplay> | 5 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | intro | course | idnumber | section | | forum | Test forum name 1 | Test forum description 1 | C1 | 0001 | 1 | | forum | Test forum name 2 | Test forum description 2 | C1 | 0002 | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I click on <targetpage> "link" in the "region-main" "region" And I add the "Recent activity" block And I open the action menu in "Recent activity" "block" And I click on "Delete Recent activity block" "link" And I press "Yes" And <belowpage> "section" <should_see_other_sections> exist And I click on "Edit settings" "link" in the "Test forum name 1" activity And I should see "Updating Forum" And I should see "Display description on course page" And I press "Save and return to course" And <belowpage> "section" <should_see_other_sections> exist And I click on "Hide" "link" in the "Test forum name 1" activity And <belowpage> "section" <should_see_other_sections> exist And I delete "Test forum name 1" activity And <belowpage> "section" <should_see_other_sections> exist And I should not see "Test forum name 1" in the "region-main" "region" And I duplicate "Test forum name 2" activity editing the new copy with: | Forum name | Edited test forum name 2 | And <belowpage> "section" <should_see_other_sections> exist And I should see "Test forum name 2" And I should see "Edited test forum name 2" And I hide section "1" And <belowpage> "section" <should_see_other_sections> exist And section "1" should be hidden And all activities in section "1" should be hidden And I show section "1" And <belowpage> "section" <should_see_other_sections> exist And section "1" should be visible And the following config values are set as admin: | unaddableblocks | | theme_boost| And I add the "Section links" block And <belowpage> "section" <should_see_other_sections> exist And I should see "1 2 3 4 5" in the "Section links" "block" And I click on "2" "link" in the "Section links" "block" And I <should_see_other_sections_following_block_sections_links> see "Test forum name 2" Examples: | courseformat | coursedisplay | targetpage | should_see_other_sections | should_see_other_sections_following_block_sections_links | belowpage | | topics | 0 | "General" | should | should | "Topic 2" | | topics | 1 | "Topic 1" | should not | should not | "Topic 2" | | topics | 1 | "General" | should | should not | "Topic 2" | | weeks | 0 | "General" | should | should | "8 January - 14 January" | | weeks | 1 | "1 January - 7 January" | should not | should not | "8 January - 14 January" | | weeks | 1 | "General" | should | should not | "8 January - 14 January" | @javascript Scenario Outline: Indentation should allow one level only Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | startdate | | Course 1 | C1 | <courseformat> | <coursedisplay> | 5 | 0 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activities" exist: | activity | name | intro | course | idnumber | | forum | Test forum name | Test forum description | C1 | forum1 | When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I open "Test forum name" actions menu Then "Move right" "link" should be visible And "Move left" "link" should not be visible And I click on "Move right" "link" in the "Test forum name" activity And I open "Test forum name" actions menu And "Move right" "link" should not be visible And "Move left" "link" should be visible And I click on "Move left" "link" in the "Test forum name" activity And I open "Test forum name" actions menu And "Move right" "link" should be visible And "Move left" "link" should not be visible Examples: | courseformat | | topics | | weeks | @javascript Scenario Outline: Admins could disable indentation Given the following "courses" exist: | fullname | shortname | format | coursedisplay | numsections | startdate | | Course 1 | C1 | <courseformat> | <coursedisplay> | 5 | 0 | And the following "activities" exist: | activity | name | intro | course | idnumber | | forum | Test forum name | Test forum description | C1 | forum1 | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on And I open "Test forum name" actions menu And "Move right" "link" should be visible And "Move left" "link" should not be visible And I click on "Move right" "link" in the "Test forum name" activity When the following config values are set as admin: | indentation | 0 | format_<courseformat> | And I am on "Course 1" course homepage with editing mode on And I open "Test forum name" actions menu Then "Move right" "link" should not exist And "Move left" "link" should not exist Examples: | courseformat | | topics | | weeks | paged_course_information.feature 0000644 00000007305 15151776370 0013207 0 ustar 00 @core @core_course Feature: Course paged mode information In order to split the course in parts As a teacher I need to display the proper section information in a paged mode course @javascript Scenario Outline: Section summary information for teachers and students in paged courses Given the following "courses" exist: | fullname | shortname | category | format | numsections | coursedisplay | enablecompletion | | Course 1 | C1 | 0 | <courseformat> | 3 | 1 | <completion> | And the following "activities" exist: | activity | course | name | section | completion | | chat | C1 | Chat room | 1 | <completion> | | data | C1 | Database | 1 | <completion> | | forum | C1 | First forum | 2 | <completion> | | forum | C1 | Second forum | 2 | <completion> | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | First | student1@example.com | | teacher1 | Teacher | First | teacher1@example.com | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | When I log in as "<user>" And I am on "Course 1" course homepage Then I should see "Chat: 1" in the "#section-1" "css_element" And I should see "Database: 1" in the "#section-1" "css_element" And I should <show> "Progress:" in the "#section-1" "css_element" And I should see "Forums: 2" in the "#section-2" "css_element" And I should <show> "Progress:" in the "#section-2" "css_element" Examples: | user | courseformat | completion | show | | student1 | topics | 0 | not see | | student1 | weeks | 0 | not see | | student1 | topics | 1 | see | | student1 | weeks | 1 | see | | teacher1 | topics | 0 | not see | | teacher1 | weeks | 0 | not see | | teacher1 | topics | 1 | see | | teacher1 | weeks | 1 | see | @javascript Scenario Outline: Section summary information for guest in paged courses Given the following "courses" exist: | fullname | shortname | category | format | numsections | coursedisplay | enablecompletion | | Course 1 | C1 | 0 | <courseformat> | 3 | 1 | <completion> | And the following "activities" exist: | activity | course | name | section | completion | | chat | C1 | Chat room | 1 | <completion> | | data | C1 | Database | 1 | <completion> | | forum | C1 | First forum | 2 | <completion> | | forum | C1 | Second forum | 2 | <completion> | And I am on the "Course 1" "enrolment methods" page logged in as admin And I click on "Enable" "link" in the "Guest access" "table_row" And I log out When I log in as "guest" And I am on "Course 1" course homepage Then I should see "Chat: 1" in the "#section-1" "css_element" And I should see "Database: 1" in the "#section-1" "css_element" And I should not see "Progress:" in the "#section-1" "css_element" And I should see "Forums: 2" in the "#section-2" "css_element" And I should not see "Progress:" in the "#section-2" "css_element" Examples: | courseformat | completion | | topics | 0 | | weeks | 0 | | topics | 1 | | weeks | 1 | activities_edit_name.feature 0000644 00000004303 15151776370 0012306 0 ustar 00 @core @core_course Feature: Edit activity name in-place In order to quickly edit activity name As a teacher I need to use inplace editing Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | And the following "courses" exist: | fullname | shortname | format | | Course 1 | C1 | topics | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | And the following "activity" exists: | course | C1 | | activity | forum | | name | Test forum name | | description | Test forum description | | idnumber | forum1 | @javascript Scenario: Edit activity name in-place When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on # Rename activity And I set the field "Edit title" in the "Test forum name" "activity" to "Good news" Then I should not see "Test forum name" in the ".course-content" "css_element" And "New name for activity Test forum name" "field" should not exist And I should see "Good news" And I am on "Course 1" course homepage And I should see "Good news" And I should not see "Test forum name" # Cancel renaming And I click on "Edit title" "link" in the "[data-value='Good news']" "css_element" And I type "Terrible news" And I press the escape key And "New name for activity Good news" "field" should not exist And I should see "Good news" And I should not see "Terrible news" And I am on "Course 1" course homepage And I should see "Good news" And I should not see "Terrible news" @javascript Scenario: Edit activity name in-place ensuring correct encoding When I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I set the field "Edit title" in the "Test forum name" "activity" to "Good & bad news" Then I should not see "Test forum name" in the ".course-content" "css_element" And I should see "Good & bad news" in the ".course-content" "css_element" activities_visibility_icons.feature 0000644 00000021207 15151776370 0013745 0 ustar 00 @core @core_course @_cross_browser Feature: Toggle activities visibility from the course page In order to delay activities availability As a teacher I need to quickly change the visibility of an activity Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "courses" exist: | fullname | shortname | format | numsections | | Course 1 | C1 | topics | 2 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | And the following "activities" exist: | activity | course | section | idnumber | name | intro | id_visible | | assign | C1 | 1 | 1 | Test assignment name | Test assignment description | 1 | @javascript Scenario: Hide/Show toggle with javascript enabled Given the following "activity" exists: | activity | forum | | course | C1 | | idnumber | C1F1 | | name | Test forum name | | intro | Test forum description | | visible | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on When I open "Test forum name" actions menu Then "Test forum name" actions menu should not have "Show" item And "Test forum name" actions menu should not have "Make available" item And "Test forum name" actions menu should not have "Make unavailable" item And I click on "Hide" "link" in the "Test forum name" activity And "Test forum name" activity should be hidden And I open "Test forum name" actions menu And "Test forum name" actions menu should not have "Hide" item # Stealth behaviour is not available by default: And "Test forum name" actions menu should not have "Make available" item And "Test forum name" actions menu should not have "Make unavailable" item And I click on "Show" "link" in the "Test forum name" activity And "Test forum name" activity should be visible And I open "Test forum name" actions menu And "Test forum name" actions menu should not have "Show" item And "Test forum name" actions menu should not have "Make available" item And "Test forum name" actions menu should not have "Make unavailable" item And I click on "Hide" "link" in the "Test forum name" activity And "Test forum name" activity should be hidden And I reload the page And "Test forum name" activity should be hidden # Make sure that "Availability" dropdown in the edit menu has two options: Show/Hide. And I open "Test forum name" actions menu And I click on "Edit settings" "link" in the "Test forum name" activity And I expand all fieldsets And the "Availability" select box should contain "Show on course page" And the "Availability" select box should not contain "Make available but don't show on course page" And the field "Availability" matches value "Hide on course page" And I press "Save and return to course" And "Test forum name" activity should be hidden And I turn editing mode off And "Test forum name" activity should be hidden And I log out # Student should not see this activity. And I log in as "student1" And I am on "Course 1" course homepage And I should not see "Test forum name" @javascript Scenario: Activities can be made available and unavailable inside a hidden section Given the following "activity" exists: | activity | forum | | course | C1 | | idnumber | C1F1 | | section | 2 | | name | Test forum name | | intro | Test forum description | | visible | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Recent activity" block When I hide section "2" Then "Test forum name" activity should be hidden And I open "Test forum name" actions menu And "Test forum name" actions menu should not have "Show" item And "Test forum name" actions menu should not have "Hide" item And "Test forum name" actions menu should not have "Make unavailable" item And I click on "Make available" "link" in the "Test forum name" activity And "Test forum name" activity should be available but hidden from course page And I open "Test forum name" actions menu And "Test forum name" actions menu should not have "Show" item And "Test forum name" actions menu should not have "Hide" item And "Test forum name" actions menu should not have "Make available" item And I click on "Make unavailable" "link" in the "Test forum name" activity And "Test forum name" activity should be hidden # Make sure that "Availability" dropdown in the edit menu has three options. And I open "Test forum name" actions menu And I click on "Edit settings" "link" in the "Test forum name" activity And I expand all fieldsets And the "Availability" select box should contain "Hide on course page" And the "Availability" select box should contain "Make available but don't show on course page" And the "Availability" select box should not contain "Show on course page" And I set the field "Availability" to "Make available but don't show on course page" And I press "Save and return to course" And "Test forum name" activity should be available but hidden from course page And I turn editing mode off And "Test forum name" activity should be available but hidden from course page And I log out # Student will not see the module on the course page but can access it from other reports and blocks: And I log in as "student1" And I am on "Course 1" course homepage And "Test forum name" activity should be hidden And I click on "Test forum name" "link" in the "Recent activity" "block" And I should see "Test forum name" And I should see "There are no discussion topics yet in this forum" @javascript Scenario: Activities can be made available but not visible on a course page Given the following config values are set as admin: | allowstealth | 1 | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add the "Recent activity" block When I open "Test assignment name" actions menu Then "Test assignment name" actions menu should not have "Show" item And "Test assignment name" actions menu should have "Hide" item And "Test assignment name" actions menu should not have "Make available" item And "Test assignment name" actions menu should not have "Make unavailable" item And I click on "Hide" "link" in the "Test assignment name" activity And "Test assignment name" activity should be hidden And I open "Test assignment name" actions menu And "Test assignment name" actions menu should have "Show" item And "Test assignment name" actions menu should not have "Hide" item And "Test assignment name" actions menu should not have "Make unavailable" item And I click on "Make available" "link" in the "Test assignment name" activity And "Test assignment name" activity should be available but hidden from course page # Make sure that "Availability" dropdown in the edit menu has three options. And I open "Test assignment name" actions menu And I click on "Edit settings" "link" in the "Test assignment name" activity And I expand all fieldsets And the "Availability" select box should contain "Show on course page" And the "Availability" select box should contain "Hide on course page" And the field "Availability" matches value "Make available but don't show on course page" And I press "Save and return to course" And "Test assignment name" activity should be available but hidden from course page And I turn editing mode off And "Test assignment name" activity should be available but hidden from course page And I log out # Student will not see the module on the course page but can access it from other reports and blocks: And I log in as "student1" And I am on "Course 1" course homepage And "Test assignment name" activity should be hidden And I click on "Test assignment name" "link" in the "Recent activity" "block" And I should see "Test assignment name" And I should see "Submission status" And I log out course_download_content_cm.feature 0000644 00000006334 15151776370 0013543 0 ustar 00 @core @core_course Feature: Activities content download can be controlled In order to allow or restrict access to download activity content As a teacher I can disable the content download of an activity Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | | manager1 | Manager | 1 | manager1@example.com | And the following "courses" exist: | fullname | shortname | | Course 1 | C1 | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | student1 | C1 | student | | manager1 | C1 | manager | And the following "activities" exist: | activity | name | intro | introformat | course | | page | Page1 | PageDesc1 | 1 | C1 | And the following "activities" exist: | activity | name | intro | introformat | course | downloadcontent | | folder | Folder1 | FolderDesc1 | 1 | C1 | 0 | And I log in as "admin" And the following config values are set as admin: | downloadcoursecontentallowed | 1 | And I log out Scenario: "Include in course content download" field default is set to "Yes" if nothing has been set Given I am on the Page1 "Page Activity editing" page logged in as admin Then the field "Include in course content download" matches value "Yes" Scenario: "Include in course content download" field is not visible if course content is disabled on site level Given I log in as "admin" And the following config values are set as admin: | downloadcoursecontentallowed | 0 | And I am on the Page1 "Page Activity editing" page Then "Include in course content download" "select" should not exist Scenario: "Include in course content download" field is visible even if course content is disabled on course level Given I log in as "admin" And I am on "Course 1" course homepage And I navigate to "Settings" in current page administration When I set the field "Enable download course content" to "No" And I press "Save and display" And I am on the Page1 "Page Activity editing" page Then "Include in course content download" "select" should exist Scenario: "Include in course content download" field should be visible but not editable for users without configuredownloadcontent capability Given I log in as "manager1" And I am on the Folder1 "Folder Activity editing" page And "Include in course content download" "field" should exist And I log out And I log in as "admin" When I set the following system permissions of "Manager" role: | capability | permission | | moodle/course:configuredownloadcontent | Prohibit | And I log out And I log in as "manager1" And I am on the Folder1 "Folder Activity editing" page Then I should see "Include in course content download" And I should see "No" And "Include in course content download" "select" should not exist course_contact.feature 0000644 00000016546 15151776370 0011164 0 ustar 00 @core @core_course Feature: Test if displaying the course contacts works correctly: As a user I need to see the course contacts of a course. As an admin I need to be able to control the appearance of the course contacts. Background: Given the following "categories" exist: | name | category | idnumber | | Cat 1 | 0 | CAT1 | And the following "courses" exist: | fullname | shortname | category | format | | Course 1 | C1 | CAT1 | topics | And the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | teacher2 | Teacher | 2 | teacher2@example.com | | teacher3 | Teacher | 3 | teacher3@example.com | | manager1 | Manager | 1 | manager1@example.com | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | teacher1 | C1 | editingteacher | | teacher1 | C1 | teacher | | teacher2 | C1 | teacher | | teacher3 | C1 | editingteacher | | manager1 | C1 | manager | Scenario: Test general course contacts functionality for all user roles Given I log in as "admin" And I navigate to "Appearance > Courses" in site administration And I set the following fields to these values: | Manager | 0 | | Teacher | 1 | | Non-editing teacher | 0 | | Display all course contact roles | 0 | And I press "Save changes" When I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should not see "Teacher 2" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" When I log out And I log in as "manager1" And I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should not see "Teacher 2" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" When I log out And I log in as "teacher1" And I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should not see "Teacher 2" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" When I log out And I log in as "student1" And I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should not see "Teacher 2" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" Scenario: Test course contact roles without displaying all roles Given I log in as "admin" And I navigate to "Appearance > Courses" in site administration And I set the following fields to these values: | Manager | 0 | | Teacher | 1 | | Non-editing teacher | 1 | | Display all course contact roles | 0 | And I press "Save changes" When I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should see "Teacher 2" in the ".teachers" "css_element" And I should see "Teacher 3" in the ".teachers" "css_element" And I should see "Teacher: Teacher 1" in the ".teachers" "css_element" And I should not see "Teacher, Non-editing teacher: Teacher 1" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" Scenario: Test course contact roles with displaying all roles and standard sorting Given I log in as "admin" And I navigate to "Appearance > Courses" in site administration And I set the following fields to these values: | Manager | 0 | | Teacher | 1 | | Non-editing teacher | 1 | | Display all course contact roles | 1 | And I press "Save changes" When I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should see "Teacher 2" in the ".teachers" "css_element" And I should see "Teacher 3" in the ".teachers" "css_element" And I should see "Teacher, Non-editing teacher: Teacher 1" in the ".teachers" "css_element" And I should not see "Teacher: Teacher 1" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" And I should see teacher "Teacher 1" before "Teacher 3" in the course contact listing And I should see teacher "Teacher 3" before "Teacher 2" in the course contact listing And I should not see teacher "Teacher 1" after "Teacher 3" in the course contact listing And I should not see teacher "Teacher 3" after "Teacher 2" in the course contact listing Scenario: Test course contact roles with displaying all roles and modified sorting Given I log in as "admin" And I navigate to "Appearance > Courses" in site administration And I set the following fields to these values: | Manager | 0 | | Teacher | 1 | | Non-editing teacher | 1 | | Display all course contact roles | 1 | And I press "Save changes" And I navigate to "Users > Permissions > Define roles" in site administration And I click on "Move up" "link" in the "//td[text()[contains(.,'Non-editing teacher')]]/parent::tr/td[contains(@class, 'lastcol')]" "xpath_element" When I am on course index And I should see "Cat 1" in the "#region-main" "css_element" And I follow "Cat 1" And I wait until the page is ready And I should see "Course 1" in the "#region-main" "css_element" Then I should see "Teacher 1" in the ".teachers" "css_element" And I should see "Teacher 2" in the ".teachers" "css_element" And I should see "Teacher 3" in the ".teachers" "css_element" And I should see "Non-editing teacher, Teacher: Teacher 1" in the ".teachers" "css_element" And I should not see "Non-editing teacher: Teacher 1" in the ".teachers" "css_element" And I should not see "Manager 1" in the ".teachers" "css_element" And I should see teacher "Teacher 1" before "Teacher 2" in the course contact listing And I should see teacher "Teacher 2" before "Teacher 3" in the course contact listing And I should not see teacher "Teacher 1" after "Teacher 2" in the course contact listing And I should not see teacher "Teacher 2" after "Teacher 3" in the course contact listing course_collapse_sections.feature 0000644 00000024156 15151776370 0013236 0 ustar 00 @core @core_course @core_courseformat Feature: Collapse course sections In order to quickly access the course structure As a user I need to collapse/extend sections for Topics/Weeks formats. Background: Given the following "users" exist: | username | firstname | lastname | email | | teacher1 | Teacher | 1 | teacher1@example.com | | student1 | Student | 1 | student1@example.com | And the following "course" exists: | fullname | Course 1 | | shortname | C1 | | category | 0 | | enablecompletion | 1 | | numsections | 5 | | startdate | 957139200 | | enablecompletion | 1 | | hiddensections | 0 | And the following "activities" exist: | activity | name | intro | course | idnumber | section | completion | | assign | Assignment 1 | Test assignment description1 | C1 | assign1 | 1 | 1 | | assign | Assignment 2 | Test assignment description2 | C1 | assign2 | 2 | 1 | | book | Book 2 | | C1 | book2 | 2 | 1 | | book | Book 3 | | C1 | book3 | 3 | 1 | | forum | Forum 4 | Test forum description4 | C1 | forum4 | 4 | 1 | | forum | Forum 5 | Test forum description5 | C1 | forum5 | 5 | 1 | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | teacher1 | C1 | editingteacher | And I log in as "admin" And I am on "Course 1" course homepage with editing mode on When I edit the section "4" And I expand all fieldsets And I press "Add restriction..." And I click on "Date" "button" in the "Add restriction..." "dialogue" And I set the field "direction" to "until" And I set the field "x[year]" to "2013" And I press "Save changes" And I hide section "5" And I log out @javascript Scenario: No chevron on site home Given I log in as "admin" And I am on site homepage And I turn editing mode on And I add a "Forum" to section "1" and I fill the form with: | Forum name | Test forum post backup name | | Description | Test forum post backup description | And I click on "Edit summary" "link" in the "region-main" "region" And I click on "Custom" "checkbox" And I set the field "New value for Section name" to "New section name" When I press "Save changes" Then "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region" @javascript Scenario: Expand/collapse sections for Topics format. Given I am on the "Course 1" course page logged in as student1 And "[data-toggle=collapse]" "css_element" should exist in the "region-main" "region" And I should see "Assignment 1" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And I should see "Book 2" in the "region-main" "region" And I should see "Book 3" in the "region-main" "region" And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I should see "2013" in the "#section-4 .availabilityinfo" "css_element" And I should not see "Forum 4" And I should see "Not available" in the "#section-5" "css_element" And I should not see "Forum 5" When I click on "#collapssesection3" "css_element" And I should see "Assignment 1" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And I should see "Book 2" in the "region-main" "region" And I should not see "Book 3" in the "region-main" "region" And I click on "#collapssesection1" "css_element" And I click on "#collapssesection2" "css_element" And I click on "#collapssesection4" "css_element" And I click on "#collapssesection5" "css_element" Then I should not see "Assignment 1" in the "region-main" "region" And I should not see "Assignment 2" in the "region-main" "region" And I should not see "Book 2" in the "region-main" "region" And I should not see "Book 3" in the "region-main" "region" And I should not see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I click on "#collapssesection1" "css_element" And I click on "#collapssesection2" "css_element" And I click on "#collapssesection3" "css_element" And I click on "#collapssesection4" "css_element" And I click on "#collapssesection5" "css_element" And I should see "Assignment 1" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And I should see "Book 2" in the "region-main" "region" And I should see "Book 3" in the "region-main" "region" And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element" @javascript Scenario: Expand/collapse sections for Weeks format. Given I am on the "Course 1" course page logged in as teacher1 When I navigate to "Settings" in current page administration And I expand all fieldsets And I set the following fields to these values: | Format | Weekly format | And I press "Save and display" And I should see "Assignment 1" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And I should see "Book 2" in the "region-main" "region" And I should see "Book 3" in the "region-main" "region" And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I should see "2013" in the "#section-4 .availabilityinfo" "css_element" And I should see "Forum 4" And I should see "Hidden from students" in the "#section-5" "css_element" And I should see "Forum 5" When I click on "#collapssesection3" "css_element" And I should see "Assignment 1" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And I should see "Book 2" in the "region-main" "region" And I should not see "Book 3" in the "region-main" "region" And I click on "#collapssesection1" "css_element" And I click on "#collapssesection2" "css_element" And I click on "#collapssesection4" "css_element" And I click on "#collapssesection5" "css_element" Then I should not see "Assignment 1" in the "region-main" "region" And I should not see "Assignment 2" in the "region-main" "region" And I should not see "Book 2" in the "region-main" "region" And I should not see "Book 3" in the "region-main" "region" And I should not see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I should not see "Not available" in the "#section-5" "css_element" And I click on "#collapssesection1" "css_element" And I click on "#collapssesection2" "css_element" And I click on "#collapssesection3" "css_element" And I click on "#collapssesection4" "css_element" And I click on "#collapssesection5" "css_element" And I should see "Assignment 1" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And I should see "Book 2" in the "region-main" "region" And I should see "Book 3" in the "region-main" "region" And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I should see "2013" in the "#section-4 .availabilityinfo" "css_element" And I should see "Forum 4" And I should see "Hidden from students" in the "#section-5" "css_element" And I should see "Forum 5" @javascript Scenario: Users don't see chevron on one section per page for Topics format Given I log in as "teacher1" And I am on "Course 1" course homepage When I navigate to "Settings" in current page administration And I expand all fieldsets And I set the following fields to these values: | Course layout | Show one section per page | And I press "Save and display" And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region" And I click on "Topic 2" "link" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region" Then "Topic 1" "section" should not exist And "Topic 3" "section" should not exist And I am on "Course 1" course homepage with editing mode on And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I should see "2013" in the "#section-4 .availabilityinfo" "css_element" And I should see "Forum 4" And I should see "Hidden from students" in the "#section-5" "css_element" And I should see "Forum 5" @javascript Scenario: Users don't see chevron on one section per page for Weeks format Given I log in as "teacher1" And I am on "Course 1" course homepage When I navigate to "Settings" in current page administration And I expand all fieldsets And I set the following fields to these values: | Course layout | Show one section per page | | Format | Weekly format | And I press "Save and display" And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region" And I click on "8 May - 14 May" "link" in the "region-main" "region" And I should see "Assignment 2" in the "region-main" "region" And "[data-toggle=collapse]" "css_element" should not exist in the "region-main" "region" Then "1 May - 7 May" "section" should not exist And "15 May - 21 May" "section" should not exist And I log out And I log in as "student1" And I am on "Course 1" course homepage And I should see "Available until" in the "#section-4 .availabilityinfo" "css_element" And I should see "2013" in the "#section-4 .availabilityinfo" "css_element" And I should not see "Forum 4" And I should see "Not available" in the "#section-5" "css_element" And I should not see "Forum 5" add_activities.feature 0000644 00000007516 15151776370 0011122 0 ustar 00 @core @core_course Feature: Add activities to courses In order to provide tools for students learning As a teacher I need to add activites to a course Background: Given the following "courses" exist: | fullname | shortname | format | | Course 1 | Course 1 | topics | @javascript Scenario: Add an activity to a course Given I am on the "Course 1" Course page logged in as admin And I am on "Course 1" course homepage with editing mode on When I add a "Database" to section "3" and I fill the form with: | Name | Test name | | Description | Test database description | | ID number | TESTNAME | | Allow comments on entries | Yes | | Force language | English | And I turn editing mode off Then I should not see "Adding a new" And I turn editing mode on And I open "Test name" actions menu And I click on "Edit settings" "link" in the "Test name" activity And the following fields match these values: | Name | Test name | | ID number | TESTNAME | | Allow comments on entries | Yes | | Force language | English (en) | @javascript Scenario: Add an activity supplying only the name Given I am on the "Course 1" Course page logged in as admin And I am on "Course 1" course homepage with editing mode on When I add a "Database" to section "3" and I fill the form with: | Name | Test name | Then I should see "Test name" @javascript Scenario: Set activity description to required then add an activity supplying only the name Given the following config values are set as admin: | requiremodintro | 1 | And I am on the "Course 1" Course page logged in as admin And I am on "Course 1" course homepage with editing mode on And I add a "Database" to section "3" and I fill the form with: | Name | Test name | Then I should see "Required" @javascript Scenario: The activity description should use the user's preferred editor on creation Given the following "user preferences" exist: | user | preference | value | | admin | htmleditor | textarea | And I am logged in as admin And I am on "Course 1" course homepage with editing mode on When I add a "Database" to section "3" Then the field "Description format" matches value "0" @javascript Scenario: The activity description should preserve the format used once edited (markdown version) Given the following "activities" exist: | activity | name | intro | introformat | course | | assign | A4 | Desc 4 | 4 | Course 1 | And the following "user preferences" exist: | user | preference | value | | admin | htmleditor | textarea | And I am logged in as admin And I am on "Course 1" course homepage with editing mode on And I open "A4" actions menu When I click on "Edit settings" "link" in the "A4" activity Then the field "Description format" matches value "4" @javascript Scenario: The activity description should preserve the format used once edited (plain text version) Given the following "activities" exist: | activity | name | intro | introformat | course | | assign | A2 | Desc 2 | 2 | Course 1 | And the following "user preferences" exist: | user | preference | value | | admin | htmleditor | textarea | And I am logged in as admin And I am on "Course 1" course homepage with editing mode on And I open "A2" actions menu When I click on "Edit settings" "link" in the "A2" activity Then the field "Description format" matches value "2" frontpage_display_modes.feature 0000644 00000007747 15151776370 0013055 0 ustar 00 @core @core_course Feature: Site home displays items in different modes In order to show a clean and clear list of the site categories and course As an admin I need to set different frontpage display modes Background: Given the following "categories" exist: | name | category | idnumber | | Category A | 0 | CATA | | Category B | 0 | CATB | | Category A child | CATA | CATA1 | | Category B child | CATB | CATB1 | | Category A child child | CATA1 | CATA11 | | Category C | 0 | CATC | And the following "courses" exist: | fullname | shortname | category | | Course 1 1 | COURSE1_1 | CATA | | Course 2 1 | COURSE2_1 | CATB | | Course 11 1 | COURSE11_1 | CATA1 | | Course 2 2 | COURSE2_2 | CATB | | Course 21 1 | COURSE21_1 | CATB1 | | Course 111 1 | COURSE111_1 | CATA11 | | Course 111 2 | COURSE111_2 | CATA11 | And I log in as "admin" @javascript Scenario: Displays a list of categories When I set the following administration settings values: | Site home items when logged in | List of categories | | Maximum category depth | 2 | And I am on site homepage Then I should see "Category A" in the "region-main" "region" And I should see "Category A child" in the "region-main" "region" And I should not see "Category A child child" in the "region-main" "region" And I toggle "Category A" category children visibility in frontpage And I should not see "Category A child" in the "region-main" "region" And I toggle "Category A" category children visibility in frontpage And I should see "Category A child" in the "region-main" "region" And I toggle "Category A child" category children visibility in frontpage And I should see "Category A child child" in the "region-main" "region" @javascript Scenario: Displays a combo list When I set the following administration settings values: | Site home items when logged in | Combo list | | Maximum category depth | 2 | And I am on site homepage Then I should see "Category A" in the "region-main" "region" And I should see "Category A child" in the "region-main" "region" And I should not see "Category A child child" in the "region-main" "region" And I should see "Course 1 1" in the "region-main" "region" And I should see "Course 2 2" in the "region-main" "region" And I should not see "Course 11 1" in the "region-main" "region" And I toggle "Category A child" category children visibility in frontpage And I should see "Course 11 1" in the "region-main" "region" And I should see "Category A child child" in the "region-main" "region" And I toggle "Category A" category children visibility in frontpage And I should not see "Course 1 1" in the "region-main" "region" And I should not see "Category A child" in the "region-main" "region" And I toggle "Category A" category children visibility in frontpage And I should see "Course 11 1" in the "region-main" "region" Scenario: Displays Enrolled users in frontpage Given the following "users" exist: | username | firstname | lastname | email | profile_field_frog | | user1 | User | One | one@example.com | Kermit | And the following "course enrolments" exist: | user | course | role | | admin | COURSE1_1 | student | | admin | COURSE2_1 | student | | admin | COURSE2_2 | student | And I set the following administration settings values: | Site home items when logged in | Enrolled courses | | frontpagecourselimit | 2 | And I log in as "admin" And I am on site homepage When I click on "My courses" "link" in the "frontpage-course-list" "region" Then I should see "My courses" in the "page-header" "region" frontpage_topic_section.feature 0000644 00000003767 15151776370 0013061 0 ustar 00 @core @core_course Feature: Site home topic section In order to show a display activities in the frontpage As an admin I need to edit the frontpage topic section Background: Given the following config values are set as admin: | numsections | 1 | Scenario: Activities should appear in frontpage Given the following "activities" exist: | activity | course | section | name | intro | idnumber | | assign | Acceptance test site | 1 | Frontpage assignment | Assignment description | assign0 | When I log in as "admin" And I am on site homepage Then I should see "Frontpage assignment" in the "region-main" "region" @javascript Scenario: Topic name does appears in frontpage Given the following "activities" exist: | activity | course | section | name | intro | idnumber | | assign | Acceptance test site | 1 | Frontpage assignment | Assignment description | assign0 | And I log in as "admin" And I am on site homepage And I turn editing mode on And I click on "Edit summary" "link" in the "region-main" "region" And I click on "Custom" "checkbox" And I set the field "New value for Section name" to "New section name" When I press "Save changes" And I should see "New section name" in the "region-main" "region" Then I turn editing mode off And I should see "New section name" in the "region-main" "region" @javascript Scenario: Topic description appears in the frontpage Given I log in as "admin" And I am on site homepage And I turn editing mode on And I click on "Edit summary" "link" in the "region-main" "region" And I set the field "Summary" to "New section description" When I press "Save changes" And I should see "New section description" in the "region-main" "region" Then I turn editing mode off And I should see "New section description" in the "region-main" "region"
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | ���֧ߧ֧�ѧ�ڧ� ����ѧߧڧ��: 0.08 |
proxy
|
phpinfo
|
���ѧ����ۧܧ�