From 93482c3a53af7361c1c46abe4f4936ccea899061 Mon Sep 17 00:00:00 2001 From: KietLy Date: Thu, 28 Dec 2017 13:23:47 +0700 Subject: [PATCH] MDL-60078 User tours accessibility: Tab should go back into the pop-up --- .../tests/behat/tour_accessibility.feature | 87 +++++++++++++++++++ lib/behat/classes/partial_named_selector.php | 6 +- lib/tests/behat/behat_general.php | 79 +++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 admin/tool/usertours/tests/behat/tour_accessibility.feature diff --git a/admin/tool/usertours/tests/behat/tour_accessibility.feature b/admin/tool/usertours/tests/behat/tour_accessibility.feature new file mode 100644 index 00000000000..4bcf6bdc91a --- /dev/null +++ b/admin/tool/usertours/tests/behat/tour_accessibility.feature @@ -0,0 +1,87 @@ +@tool @tool_usertours +Feature: Apply accessibility to a tour + + @javascript + Scenario: Check tabbing working correctly. + Given the following "courses" exist: + | fullname | shortname | + | Course 1 | C1 | + And I log in as "admin" + And I open the User tour settings page + And I click on "Enable" "link" in the "Boost - course view" "table_row" + And I am on "Course 1" course homepage + # First dialogue of the tour, "Welcome". It has Close, Next and End buttons. + # Nothing highlighted on the page. Initially whole dialogue focused. + And I wait "1" seconds + When I press tab + Then the focused element is ".close" "css_element" in the "Welcome" "dialogue" + When I press tab + Then the focused element is "Next" "button" in the "Welcome" "dialogue" + When I press tab + Then the focused element is "End 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 ".close" "css_element" 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 "End tour" "button" in the "Welcome" "dialogue" + + When I press "Next" + # Now we are on the "Customisation" step, so Previous is also enabled. + # Also, the "Course Header" section in the page is highlighted, and this + # section contain breadcrumb Dashboard / Course 1 / C1 and setting drop down, + # so the focus have to go though them and back to the dialogue. + And I wait "1" seconds + And I press tab + Then the focused element is ".close" "css_element" in the "Customisation" "dialogue" + When I press tab + Then the focused element is "Previous" "button" in the "Customisation" "dialogue" + When I press tab + Then the focused element is "Next" "button" in the "Customisation" "dialogue" + When I press tab + Then the focused element is "End tour" "button" in the "Customisation" "dialogue" + # We tab 3 times from "End Tour" button to header container, drop down then go to "Dashboard" link. + When I press tab + And I press tab + And I press tab + Then the focused element is "Dashboard" "link" in the ".breadcrumb" "css_element" + When I press tab + Then the focused element is "Courses" "link" + When I press tab + Then the focused element is "C1" "link" + # Standing at final element of "Course Header" section, tab twice will lead our focus back to + # whole dialog then to close button on dialog header. + When I press tab + And I press tab + Then the focused element is ".close" "css_element" in the "Customisation" "dialogue" + # Press shift-tab twice should lead us back to "C1" link. + When I press shift tab + And I press shift tab + Then the focused element is "C1" "link" + + When I press "Next" + # Now we are on the "Navigation" step, so Previous is also enabled. + # Also, the "Side panel" button in the page is highlighted, and this comes + # in the tab order after End buttons, and before focus loops back to the popup. + And I wait "1" seconds + And I press tab + Then the focused element is ".close" "css_element" in the "Navigation" "dialogue" + When I press tab + Then the focused element is "Previous" "button" in the "Navigation" "dialogue" + When I press tab + Then the focused element is "Next" "button" in the "Navigation" "dialogue" + When I press tab + Then the focused element is "End tour" "button" in the "Navigation" "dialogue" + When I press tab + Then the focused element is "Side panel" "button" + When I press tab + # Here the focus loops round to the whole dialogue again. + And I press tab + Then the focused element is ".close" "css_element" in the "Navigation" "dialogue" + When I press shift tab + And I press shift tab + Then the focused element is "Side panel" "button" + When I press shift tab + And the focused element is "End tour" "button" in the "Navigation" "dialogue" diff --git a/lib/behat/classes/partial_named_selector.php b/lib/behat/classes/partial_named_selector.php index 1faa0db4c80..cf78531251f 100644 --- a/lib/behat/classes/partial_named_selector.php +++ b/lib/behat/classes/partial_named_selector.php @@ -137,7 +137,11 @@ XPATH .//div[contains(concat(' ', normalize-space(@class), ' '), ' yui-dialog ') and normalize-space(descendant::div[@class='hd']) = %locator%] | -.//div[@data-region='modal' and descendant::*[@data-region='title'] = %locator%] +.//div[@data-region='modal' and descendant::*[@data-region='title'] = %locator%] | +.//div[contains(concat(' ', normalize-space(@class), ' '), ' modal-content ') and + normalize-space(descendant::h4[ + contains(concat(' ', normalize-space(@class), ' '), ' modal-title ') + ]) = %locator%] XPATH , 'icon' => <<(?:[^"]|\\")*)" "(?P[^"]*)"$/ + * @param string $not optional step verifier + * @param string $nodeelement Element identifier + * @param string $nodeselectortype Element type + * @throws ErrorException If not using JavaScript + * @throws ExpectationException + */ + public function the_focused_element_is($not, $nodeelement, $nodeselectortype) { + if (!$this->running_javascript()) { + throw new ErrorException('Checking focus on an element requires JavaScript'); + } + list($a, $b) = $this->transform_selector($nodeselectortype, $nodeelement); + $element = $this->find($a, $b); + $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->getSession()->evaluateScript($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(?:[^"]|\\")*)" "(?P[^"]*)" in the "(?P(?:[^"]|\\")*)" "(?P[^"]*)"$/ + * @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 ErrorException 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 ErrorException('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->getSession()->evaluateScript($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 (!$this->running_javascript()) { + throw new DriverException($shift . ' Tab press step is not available with Javascript disabled'); + } + + $value = ($shift == ' shift') ? [\WebDriver\Key::SHIFT . \WebDriver\Key::TAB] : [\WebDriver\Key::TAB]; + $this->getSession()->getDriver()->getWebDriverSession()->activeElement()->postValue(['value' => $value]); + } }