From e9b443b3a15432b94806634a6b9c09c17eb6695d Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Wed, 17 Jun 2020 14:50:04 +0800 Subject: [PATCH] MDL-70148 behat: Add steps to send keys without an element --- admin/tool/behat/tests/behat/keyboard.feature | 52 ++++++ lib/behat/behat_base.php | 42 +++++ lib/tests/behat/behat_general.php | 149 +++++++++++++++++- 3 files changed, 237 insertions(+), 6 deletions(-) create mode 100644 admin/tool/behat/tests/behat/keyboard.feature diff --git a/admin/tool/behat/tests/behat/keyboard.feature b/admin/tool/behat/tests/behat/keyboard.feature new file mode 100644 index 00000000000..ca9a74f02f3 --- /dev/null +++ b/admin/tool/behat/tests/behat/keyboard.feature @@ -0,0 +1,52 @@ +@tool_behat +Feature: Verify that keyboard steps work as expected + In order to use behat step definitions + As a test writer + I need to verify that the keyboard steps work as expected + + @javascript + Scenario: Typing keys into a field causes them to be input + Given the following "users" exist: + | username | email | firstname | lastname | password | + | saffronr | saffron.rutledge@example.com | Saffron | Rutledge | flowerpower | + Given I click on "Log in" "link" + And I click on "Username" "field" + When I type "saffronr" + And I press the tab key + And I type "flowerpower" + And I press enter + Then I should see "You are logged in as Saffron Rutledge" + + @javascript + Scenario: Using tab changes focus to the next or previous field + Given I click on "Log in" "link" + And I click on "Username" "field" + And the focused element is "Username" "field" + When I press the tab key + Then the focused element is "Password" "field" + + And I press the shift tab key + And the focused element is "Username" "field" + + @javascript + Scenario: Using the arrow keys allows me to navigate through menus + Given the following "users" exist: + | username | email | firstname | lastname | + | saffronr | saffron.rutledge@example.com | Saffron | Rutledge | + And I log in as "saffronr" + And I click on "Saffron Rutledge" "link" in the ".usermenu" "css_element" + When I press the up key + Then the focused element is "Log out" "link" + + @javascript + Scenario: The escape key can be used to close a dialogue + Given the following "course" exists: + | fullname | C1| + | shortname | C1 | + And I log in as "admin" + And I am on "C1" course homepage + And I navigate to course participants + And I press "Enrol users" + And "Enrol users" "dialogue" should be visible + When I press the escape key + Then "Enrol users" "dialogue" should not be visible diff --git a/lib/behat/behat_base.php b/lib/behat/behat_base.php index db165c7101c..57688c8d671 100644 --- a/lib/behat/behat_base.php +++ b/lib/behat/behat_base.php @@ -38,6 +38,10 @@ use Behat\Mink\Session; require_once(__DIR__ . '/classes/component_named_selector.php'); require_once(__DIR__ . '/classes/component_named_replacement.php'); +// Alias the WebDriver\Key class to behat_keys to make future transition to a different WebDriver implementation +// easier. +class_alias('WebDriver\\Key', 'behat_keys'); + /** * Steps definitions base class. * @@ -264,6 +268,44 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { ]; } + /** + * Send key presses straight to the currently active element. + * + * The `$keys` array contains a list of key values to send to the session as defined in the WebDriver and JsonWire + * specifications: + * - JsonWire: https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#sessionsessionidkeys + * - W3C WebDriver: https://www.w3.org/TR/webdriver/#keyboard-actions + * + * This may be a combination of typable characters, modifier keys, and other supported keypoints. + * + * The NULL_KEY should be used to release modifier keys. If the NULL_KEY is not used then modifier keys will remain + * in the pressed state. + * + * Example usage: + * + * behat_base::type_keys($this->getSession(), [behat_keys::SHIFT, behat_keys::TAB, behat_keys::NULL_KEY]); + * behat_base::type_keys($this->getSession(), [behat_keys::ENTER, behat_keys::NULL_KEY]); + * behat_base::type_keys($this->getSession(), [behat_keys::ESCAPE, behat_keys::NULL_KEY]); + * + * It can also be used to send text input, for example: + * + * behat_base::type_keys( + * $this->getSession(), + * ['D', 'o', ' ', 'y', 'o', 'u', ' ', 'p', 'l', 'a' 'y', ' ', 'G', 'o', '?', behat_base::NULL_KEY] + * ); + * + * + * Please note: This function does not use the element/sendKeys variants but sends keys straight to the browser. + * + * @param Session $session + * @param string[] $keys + */ + public static function type_keys(Session $session, array $keys): void { + $session->getDriver()->getWebDriverSession()->keys([ + 'value' => $keys, + ]); + } + /** * Finds DOM nodes in the page using named selectors. * diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 49b2fdd63c0..469fe0d715f 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -27,12 +27,12 @@ require_once(__DIR__ . '/../../behat/behat_base.php'); -use Behat\Mink\Exception\ExpectationException as ExpectationException, - Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException, - Behat\Mink\Exception\DriverException as DriverException, - WebDriver\Exception\NoSuchElement as NoSuchElement, - WebDriver\Exception\StaleElementReference as StaleElementReference, - Behat\Gherkin\Node\TableNode as TableNode; +use Behat\Gherkin\Node\TableNode as TableNode; +use Behat\Mink\Exception\DriverException as DriverException; +use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; +use Behat\Mink\Exception\ExpectationException as ExpectationException; +use WebDriver\Exception\NoSuchElement as NoSuchElement; +use WebDriver\Exception\StaleElementReference as StaleElementReference; /** * Cross component steps definitions. @@ -1710,6 +1710,143 @@ EOF; } } + /** + * 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 { + behat_base::type_keys($this->getSession(), str_split($keys)); + } + + /** + * Press a named 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 + * + * 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.* )?(?P.*) 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::UP_ARROW; + break; + case 'DOWN': + $keys[] = behat_keys::DOWN_ARROW; + break; + case 'LEFT': + $keys[] = behat_keys::LEFT_ARROW; + break; + case 'RIGHT': + $keys[] = behat_keys::RIGHT_ARROW; + 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; + default: + throw new \coding_exception("Unknown key '$key'}"); + } + + // Always send the NULL key as the last key. + $keys[] = behat_keys::NULL_KEY; + + behat_base::type_keys($this->getSession(), $keys); + } + /** * Trigger a keydown event for a key on a specific element. * -- 2.43.0