From b8e6c928b631d7023bc4e1e2a7b8c4bc84c9834c Mon Sep 17 00:00:00 2001 From: gthomas2 Date: Tue, 26 Jan 2016 15:05:11 +0000 Subject: [PATCH 1/2] INT-8932: Add tests for section conditional restrictions --- tests/behat/behat_theme_snap.php | 428 +++++++++++++++++- .../course_conditional_restrictions.feature | 88 ++++ 2 files changed, 509 insertions(+), 7 deletions(-) create mode 100644 tests/behat/course_conditional_restrictions.feature diff --git a/tests/behat/behat_theme_snap.php b/tests/behat/behat_theme_snap.php index bcb01f98d..80d3e7bdc 100644 --- a/tests/behat/behat_theme_snap.php +++ b/tests/behat/behat_theme_snap.php @@ -28,6 +28,7 @@ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); use Behat\Behat\Context\Step\Given, + Behat\Mink\Element\NodeElement, Behat\Mink\Exception\ExpectationException as ExpectationException; /** @@ -40,6 +41,24 @@ */ class behat_theme_snap extends behat_base { + /** + * Process givens array + * @param array $givens + * @return array + */ + protected function process_givens_array(array $givens) { + $givens = array_map(function($given){ + if (is_string($given)) { + return new Given($given); + } else if ($given instanceof Given) { + return $given; + } else { + throw new coding_exception('Given must be a string or Given instance'); + } + }, $givens); + return $givens; + } + /** * Waits until the provided element selector is visible. * @@ -65,6 +84,7 @@ public function i_log_in_with_snap_as($username) { // Generic steps (we will prefix them later expanding the navigation dropdown if necessary). $steps = array( new Given('I click on "' . get_string('login') . '" "link"'), + new Given('I should not see "Log out"'), new Given('I wait until "#loginbtn" "css_element" is visible'), new Given('I set the field "' . get_string('username') . '" to "' . $this->escape($username) . '"'), new Given('I set the field "' . get_string('password') . '" to "'. $this->escape($username) . '"'), @@ -112,9 +132,11 @@ public function i_follow_href($link) { /** * @param int $section - * @Given /^I go to course section (\d+)$/ + * @Given /^I go to single course section (\d+)$/ */ - public function i_go_to_course_section($section) { + public function i_go_to_single_course_section($section) { + $generalcontext = behat_context_helper::get('behat_general'); + $generalcontext->wait_until_the_page_is_ready(); $currenturl = $this->getSession()->getCurrentUrl(); if (stripos($currenturl, 'course/view.php') === false) { throw new ExpectationException('Current page is not a course page!', $this->getSession()); @@ -128,6 +150,30 @@ public function i_go_to_course_section($section) { $this->getSession()->visit($newurl); } + /** + * @param int $section + * @Given /^I go to course section (\d+)$/ + */ + public function i_go_to_course_section($section) { + $generalcontext = behat_context_helper::get('behat_general'); + $generalcontext->wait_until_the_page_is_ready(); + $session = $this->getSession(); + $currenturl = $session->getCurrentUrl(); + if (stripos($currenturl, 'course/view.php') === false) { + throw new ExpectationException('Current page is not a course page!', $session); + } + $session->executeScript('location.hash = "'.'section-'.$section.'";'); + + $givens = [ + 'I wait until the page is ready', + 'I wait until "#section-'.$section.'" "css_element" is visible' + ]; + $givens = array_map(function($given){ + return new Given($given); + }, $givens); + return $givens; + } + /** * @param string * @return array @@ -138,7 +184,7 @@ public function i_can_see_course_in_all_sections_mode($course) { 'I follow "Menu"', 'Snap I follow link "'.$course.'"', 'I wait until the page is ready', - 'I go to course section 1', + 'I go to single course section 1', '".section-navigation.navigationtitle" "css_element" should not exist', # In the above, .section-navigation.navigationtitle relates to the element on the page which contains the single # section at a time navigation. Visually you would see a link on the left entitled "General" and a link on the right @@ -158,16 +204,14 @@ public function i_can_see_course_in_all_sections_mode($course) { * @Given /^Snap I log out$/ */ public function i_log_out() { + $this->getSession()->executeScript("window.scrollTo(0, 0);"); $givens = [ 'I follow "Menu"', 'I wait until ".btn.logout" "css_element" is visible', 'I follow "Log out"', 'I wait until the page is ready' ]; - $givens = array_map(function($given){ - return new Given($given); - }, $givens); - return $givens; + return $this->process_givens_array($givens); } /** @@ -202,4 +246,374 @@ public function i_open_the_personal_menu() { return null; } } + + /** + * Checks that the provided node is visible. + * + * @throws ExpectationException + * @param NodeElement $node + * @param int $timeout + * @param null|ExpectationException $exception + * @return bool + */ + protected function is_node_visible(NodeElement $node, + $timeout = self::EXTENDED_TIMEOUT, + ExpectationException $exception = null) { + + // If an exception isn't specified then don't throw an error if visibility can't be evaluated. + $dontthrowerror = empty($exception); + + // Exception for timeout checking visibility. + $msg = 'Something went wrong whilst checking visibility'; + $exception = new ExpectationException($msg, $this->getSession()); + + $visible = false; + + try { + $visible = $this->spin( + function ($context, $args) { + if ($args->isVisible()) { + return true; + } + return false; + }, + $node, + $timeout, + $exception, + true + ); + } catch (Exception $e) { + if (!$dontthrowerror) { + throw $exception; + } + } + return $visible; + } + + /** + * Clicks link with specified id|title|alt|text. + * + * @When /^I follow visible link "(?P(?:[^"]|\\")*)"$/ + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $link + */ + public function click_visible_link($link) { + $linknode = $this->find_link($link); + if (!$linknode) { + $msg = 'The "' . $linknode->getXPath() . '" xpath node could not be found'; + throw new ExpectationException($msg, $this->getSession()); + } + + // See if the first node is visible and if so click it. + if ($this->is_node_visible($linknode)) { + $linknode->click(); + return; + } + + // The first node on the page isn't visible so we are going to have to get all nodes with the same xpath. + // Extract xpath from the first node we found. + $xpath = $linknode->getXpath(); + $matches = []; + if (preg_match_all('|^\(//html/(.*)(?=\)\[1\]$)|', $xpath, $matches) !== false) { + $xpath = $matches[1][0]; + } else { + throw new coding_exception('Failed to extract xpath from '.$xpath); + } + + // Now get all nodes. + $linknodes = $this->find_all('xpath', $xpath); + + // Cycle through all nodes and if just one of them is visible break loop. + foreach ($linknodes as $node) { + if ($node === $linknode) { + // We've already tested the first node, skip it. + continue; + } + $visible = $this->is_node_visible($node, self::REDUCED_TIMEOUT); + if ($visible) { + break; + } + } + + if (!$visible) { + // Oh dear, none of the links were visible. + $msg = 'At least one node should be visible for the xpath "' . $node->getXPath(); + throw new ExpectationException($msg, $this->getSession()); + } + + // Let's also scroll to the node and make sure its in the viewport. + $this->scroll_to_node($node); + + // Hurray, we found a visible link - let's click it! + $node->click(); + } + + /** + * Scroll to node + * @param NodeElement $node + */ + protected function scroll_to_node(NodeElement $node) { + $nodexpath = str_replace('"', '\"', $node->getXpath()); + $script = + <<getSession()->executeScript($script); + } + + /** + * Make sure element is within viewport + * + * @param string $element + * @param string $selectortype + * @Given /^I scroll until "(?P(?:[^"]|\\")*)" "(?P[^"]*)" is visible$/ + */ + public function i_scroll_to_element($element, $selectortype) { + // Getting Mink selector and locator. + list($selector, $locator) = $this->transform_selector($selectortype, $element); + + // Get node. + $node = $this->find($selector, $locator); + $this->scroll_to_node($node); + } + + /** + * Presses button with specified id|name|title|alt|value. + * + * @When /^I press "(?P(?:[^"]|\\")*)" \(theme_snap\)$/ + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $button + */ + public function snap_press_button($button) { + // Ensures the button is present. + $buttonnode = $this->find_button($button); + $this->scroll_to_node($buttonnode); + $this->ensure_node_is_visible($buttonnode); + $buttonnode->press(); + } + + /** + * List steps required for adding a date restriction + * @param int $datetime + * @param string $savestr + * @return array + */ + protected function add_date_restriction($datetime, $savestr) { + + $year = date('Y', $datetime); + $month = date('n', $datetime); + $day = date('j', $datetime); + + $givens = [ + 'I expand all fieldsets', + 'I click on "Add restriction..." "button"', + '"Add restriction..." "dialogue" should be visible', + 'I click on "Date" "button" in the "Add restriction..." "dialogue"', + 'I set the field "day" to "'.$day.'"', + 'I set the field "Month" to "'.$month.'"', + 'I set the field "year" to "'.$year.'"', + 'I press "'.$savestr.'" (theme_snap)', + 'I wait until the page is ready' + ]; + + return $givens; + } + + /** + * Restrict a course section by date. + * @param int $section + * @param string $date + * @Given /^I restrict course section (?P(?:\d+)) by date to "(?P(?:[^"]|\\")*)"$/ + */ + public function i_restrict_course_section_by_date($section, $date) { + $datetime = strtotime($date); + + $givens = [ + 'I go to course section '.$section, + 'I follow visible link "Edit Topic"', + 'I wait until ".snap-form-advanced" "css_element" is visible', + 'I set the field "name" to "Topic '.$date.' '.$section.'"', + ]; + + $givens = array_merge($givens, $this->add_date_restriction($datetime, 'Save changes')); + + return $this->process_givens_array($givens); + } + + /** + * Restrict a course asset by date. + * @param string $assettitle + * @param string $date + * @Given /^I restrict course asset "(?P(?:[^"]|\\")*)" by date to "(?P(?:[^"]|\\")*)"$/ + */ + public function i_restrict_asset_by_date($assettitle, $date) { + $datetime = strtotime($date); + + $givens = [ + 'I follow asset link "'.$assettitle.'"', + 'I click on "#admin-menu-trigger" "css_element"', + 'I wait until ".block_settings.state-visible" "css_element" is visible', + 'I navigate to "Edit settings" node in "Assignment administration"' + ]; + + $givens = array_merge($givens, $this->add_date_restriction($datetime, 'Save and return to course')); + + return $this->process_givens_array($givens); + } + + /** + * Check conditional date message in given element. + * @param string $date + * @param string $element + * @param string $selectortype + * @Given /^I should see available from date of "(?P(?:[^"]|\\")*)" in "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)"$/ + */ + public function i_should_see_available_from_in_element($date, $element, $selectortype) { + $datetime = strtotime($date); + + $date = userdate($datetime, + get_string('strftimedate', 'langconfig')); + + $givens = [ + 'I should see "Available from" in the "'.$element.'" "'.$selectortype.'"', + 'I should see "'.$date.'" in the "'.$element.'" "'.$selectortype.'"', + ]; + return $this->process_givens_array($givens); + } + + /** + * Check conditional date message does not exist in given element. + * @param string $date + * @param string $element + * @param string $selectortype + * @Given /^I should not see available from date of "(?P(?:[^"]|\\")*)" in "(?P(?:[^"]|\\")*)" "(?P(?:[^"]|\\")*)"$/ + */ + public function i_should_not_see_available_from_in_element($date, $element, $selectortype) { + $datetime = strtotime($date); + + $date = userdate($datetime, + get_string('strftimedate', 'langconfig')); + + $givens = [ + 'I should not see "Available from" in the "'.$element.'" "'.$selectortype.'"', + 'I should not see "'.$date.'" in the "'.$element.'" "'.$selectortype.'"', + ]; + return $this->process_givens_array($givens); + } + + /** + * Check conditional date message in nth asset within section x. + * @param string $date + * @param string $nthasset + * @param int $section + * @Given /^I should see available from date of "(?P(?:[^"]|\\")*)" in the (?P(?:\d+st|\d+nd|\d+rd|\d+th)) asset within section (?P(?:\d+))$/ + */ + public function i_should_see_available_from_in_asset($date, $nthasset, $section) { + $nthasset = intval($nthasset); + $elementselector = '#section-'.$section.' li.snap-asset:nth-of-type('.$nthasset.')'; + return $this->i_should_see_available_from_in_element($date, $elementselector, 'css_element'); + } + + /** + * Check conditional date message not in nth asset within section x. + * @param string $date + * @param string $nthasset + * @param int $section + * @Given /^I should not see available from date of "(?P(?:[^"]|\\")*)" in the (?P(?:\d+st|\d+nd|\d+rd|\d+th)) asset within section (?P(?:\d+))$/ + */ + public function i_should_not_see_available_from_in_asset($date, $nthasset, $section) { + $nthasset = intval($nthasset); + $elementselector = '#section-'.$section.' li.snap-asset:nth-of-type('.$nthasset.')'; + return $this->i_should_not_see_available_from_in_element($date, $elementselector, 'css_element'); + } + + /** + * Check conditional date message in section. + * @param string $date + * @param int $section + * @Given /^I should see available from date of "(?P(?:[^"]|\\")*)" in section (?P(?:\d+))$/ + */ + public function i_should_see_available_from_in_section($date, $section) { + $elementselector = '#section-'.$section.' > div.content > div.snap-restrictions-meta'; + return $this->i_should_see_available_from_in_element($date, $elementselector, 'css_element'); + } + + /** + * Check conditional date message not in section. + * @param string $date + * @param int $section + * @Given /^I should not see available from date of "(?P(?:[^"]|\\")*)" in section (?P(?:\d+))$/ + */ + public function i_should_not_see_available_from_in_section($date, $section) { + $elementselector = '#section-'.$section.' > div.content > div.snap-restrictions-meta'; + return $this->i_should_not_see_available_from_in_element($date, $elementselector, 'css_element'); + } + + + /** + * @param string $text + * @param int $tocitem + * @Given /^I should see "(?P(?:[^"]|\\")*)" in TOC item (?P(?:\d+))$/ + */ + public function i_should_see_in_toc_item($text, $tocitem) { + $tocitem++; // Ignore introduction item. + $givens = [ + 'I should see "'.$text.'" in the "#chapters li:nth-of-type('.$tocitem.')" "css_element"' + ]; + return $this->process_givens_array($givens); + } + + /** + * @param string $text + * @param int $tocitem + * @Given /^I should not see "(?P(?:[^"]|\\")*)" in TOC item (?P(?:\d+))$/ + */ + public function i_should_not_see_in_toc_item($text, $tocitem) { + $tocitem++; // Ignore introduction item. + $givens = [ + 'I should not see "'.$text.'" in the "#chapters li:nth-of-type('.$tocitem.')" "css_element"' + ]; + return $this->process_givens_array($givens); + } + + /** + * Open an assignment or resource based on title. + * + * @param string $assettitle + * @throws ExpectationException + * @Given /^I follow asset link "(?P(?:[^"]|\\")*)"$/ + */ + public function i_follow_asset_link($assettitle) { + $xpath = '//a/span[contains(.,"'.$assettitle.'")]'; + + // Now get all nodes. + $linknodes = $this->find_all('xpath', $xpath); + + // Cycle through all nodes and if just one of them is visible break loop. + foreach ($linknodes as $node) { + $visible = $this->is_node_visible($node, self::REDUCED_TIMEOUT); + if ($visible) { + break; + } + } + + if (!$visible) { + // Oh dear, none of the links were visible. + $msg = 'At least one node should be visible for the xpath "' . $node->getXPath(); + throw new ExpectationException($msg, $this->getSession()); + } + + // Hurray, we found a visible link - let's click it! + $node->click(); + } } diff --git a/tests/behat/course_conditional_restrictions.feature b/tests/behat/course_conditional_restrictions.feature new file mode 100644 index 000000000..8c095fdd9 --- /dev/null +++ b/tests/behat/course_conditional_restrictions.feature @@ -0,0 +1,88 @@ +# 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 . +# +# Tests for conditional resources. +# +# @package theme_snap +# @author 2015 Guy Thomas +# @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + + +@theme @theme_snap +Feature: When the moodle theme is set to Snap, conditional restrictions work as normal. + + Background: + Given the following config values are set as admin: + | theme | snap | + | thememobile | snap | + | enablecompletion | 1 | + | enableavailability | 1 | + And the following "courses" exist: + | fullname | shortname | category | groupmode | enablecompletion | + | Course 1 | C1 | 0 | 1 | 1 | + And the following "activities" exist: + | activity | course | idnumber | name | intro | section | assignsubmission_onlinetext_enabled | + | assign | C1 | assign1 | S1 Restricted - date past | Restricted by date past | 1 | 1 | + | assign | C1 | assign2 | S1 Restricted - date future | Restricted by date future | 1 | 1 | + | assign | C1 | assign3 | S2 Restricted - date past | Restricted by date past | 2 | 1 | + | assign | C1 | assign4 | S2 Restricted - date future | Restricted by date future | 2 | 1 | + 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 | + + @javascript + Scenario: Conditionally restricted section notices show for students only when restrictions not met but always show for teachers. + Given I log in with snap as "teacher1" + And I open the personal menu + And Snap I follow link "Course 1" + And I wait until the page is ready + And I go to course section 1 + And I restrict course asset "S1 Restricted - date past" by date to "yesterday" + And I restrict course asset "S1 Restricted - date future" by date to "tomorrow" + And I should see available from date of "yesterday" in the 1st asset within section 1 + And I should see available from date of "tomorrow" in the 2nd asset within section 1 + And I go to course section 2 + And I restrict course asset "S2 Restricted - date past" by date to "yesterday" + And I restrict course asset "S2 Restricted - date future" by date to "tomorrow" + And I should see available from date of "yesterday" in the 1st asset within section 2 + And I should see available from date of "tomorrow" in the 2nd asset within section 2 + And I restrict course section 1 by date to "yesterday" + And I restrict course section 2 by date to "tomorrow" + And I should see "Conditional" in TOC item 1 + And I should see "Conditional" in TOC item 2 + And I should not see "Conditional" in TOC item 3 + And I go to course section 1 + And I should see available from date of "yesterday" in section 1 + And I go to course section 2 + And I should see available from date of "tomorrow" in section 2 + And Snap I log out + And I log in with snap as "student1" + And I open the personal menu + And Snap I follow link "Course 1" + And I wait until the page is ready + And I should not see "Conditional" in TOC item 1 + And I should see "Conditional" in TOC item 2 + And I should not see "Conditional" in TOC item 3 + And I go to course section 1 + And I should not see available from date of "yesterday" in section 1 + And I should see available from date of "tomorrow" in the 2nd asset within section 1 + And I go to course section 2 + And I should see available from date of "tomorrow" in section 2 + And "#section-2 li.snap-activity" "css_element" should not exist From 6fdceaf8f8bb752aa95f5bbf0e60a2db35d6388f Mon Sep 17 00:00:00 2001 From: gthomas2 Date: Thu, 18 Feb 2016 12:56:07 +0000 Subject: [PATCH 2/2] INT-8925: Fixed issue of bad negative test relating to ticket this branch depends on --- tests/behat/create_section_from_toc.feature | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/behat/create_section_from_toc.feature b/tests/behat/create_section_from_toc.feature index 1516d7bf9..0cde2b543 100644 --- a/tests/behat/create_section_from_toc.feature +++ b/tests/behat/create_section_from_toc.feature @@ -63,10 +63,10 @@ Feature: In the Snap theme, within a course, editing teachers can create a new s And I open the personal menu # Negative test - the single activity course should not allow for section creation via the toc. And Snap I follow link "Single activity course" - And "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" # Negative test - the social course should not allow for section creation via the toc. And Snap I follow link "Social course" - And "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" # Make sure student can see the sections created by the teacher in Topics and Weeks format courses. And Snap I log out And I log in with snap as "student1" @@ -82,23 +82,23 @@ Feature: In the Snap theme, within a course, editing teachers can create a new s Given I log in with snap as "teacher2" And I open the personal menu And Snap I follow link "Topics course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And I open the personal menu And Snap I follow link "Weeks course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And Snap I follow link "Single activity course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And Snap I follow link "Social course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And Snap I log out And I log in with snap as "student1" And I open the personal menu And Snap I follow link "Topics course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And I open the personal menu And Snap I follow link "Weeks course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And Snap I follow link "Single activity course" - Then "Create a new section" "css_element" should not exist + Then I should not see "Create a new section" in the "#page-header" "css_element" And Snap I follow link "Social course" - Then "Create a new section" "css_element" should not exist \ No newline at end of file + Then I should not see "Create a new section" in the "#page-header" "css_element" \ No newline at end of file