diff --git a/admin/tool/behat/renderer.php b/admin/tool/behat/renderer.php index 1b29a2cd63a..0df7e3a7b98 100644 --- a/admin/tool/behat/renderer.php +++ b/admin/tool/behat/renderer.php @@ -86,6 +86,8 @@ class tool_behat_renderer extends plugin_renderer_base { $stepsdefinitions = get_string('nostepsdefinitions', 'tool_behat'); } else { + $stepsdefinitions = implode('', $stepsdefinitions); + // Replace text selector type arguments with a user-friendly select. $stepsdefinitions = preg_replace_callback('/(TEXT_SELECTOR_STRING)/', function ($matches) { diff --git a/lib/behat/behat_base.php b/lib/behat/behat_base.php index d703a77ce31..1f3ca3e72c9 100644 --- a/lib/behat/behat_base.php +++ b/lib/behat/behat_base.php @@ -59,7 +59,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { * @param string $path * @return string */ - protected function locatePath($path) { + protected function locate_path($path) { $startUrl = rtrim($this->getMinkParameter('base_url'), '/') . '/'; return 0 !== strpos($path, 'http') ? $startUrl . ltrim($path, '/') : $path; } @@ -68,9 +68,9 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { * Adapter to Behat\Mink\Element\Element::find() using the spin() method. * * @link http://mink.behat.org/#traverse-the-page-selectors - * @param Exception $exception Otherwise we throw expcetion with generic info * @param string $selector The selector type (css, xpath, named...) * @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator... + * @param Exception $exception Otherwise we throw exception with generic info * @return NodeElement */ protected function find($selector, $locator, $exception = false) { @@ -216,4 +216,56 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { throw $exception; } + /** + * Gets a NodeElement based on the locator and selector type received as argument from steps definitions. + * + * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $selectortype + * @param string $element + * @return NodeElement + */ + protected function get_selected_node($selectortype, $element) { + + // Getting Mink selector and locator. + list($selector, $locator) = $this->transform_selector($selectortype, $element); + + // Returns the NodeElement. + return $this->find($selector, $locator); + } + + /** + * Transforms from step definition's argument style to Mink format. + * + * Mink has 3 different selectors css, xpath and named, where named + * selectors includes link, button, field... to simplify and group multiple + * steps in one we use the same interface, considering all link, buttons... + * at the same level as css selectors and xpath; this method makes the + * conversion from the arguments received by the steps to the selectors and locators + * required to interact with Mink. + * + * @throws ExpectationException + * @param string $selectortype It can be css, xpath or any of the named selectors. + * @param string $element The locator (or string) we are looking for. + * @return array Contains the selector and the locator expected by Mink. + */ + protected function transform_selector($selectortype, $element) { + + // Here we don't know if a $allowedtextselector is used. + if (!isset(behat_command::$allowedselectors[$selectortype])) { + throw new ExpectationException('The "' . $selectortype . '" selector type does not exist', $this->getSession()); + } + + // CSS and XPath selectors locator is one single argument. + if ($selectortype == 'css_element' || $selectortype == 'xpath_element') { + $selector = str_replace('_element', '', $selectortype); + $locator = $element; + } else { + // Named selectors uses arrays as locators including the type of named selector. + $locator = array($selectortype, $this->getSession()->getSelectorsHandler()->xpathLiteral($element)); + $selector = 'named'; + } + + return array($selector, $locator); + } + } diff --git a/lib/behat/classes/behat_command.php b/lib/behat/classes/behat_command.php index 8bdb123c34c..b4537c846d4 100644 --- a/lib/behat/classes/behat_command.php +++ b/lib/behat/classes/behat_command.php @@ -42,6 +42,34 @@ class behat_command { */ const DOCS_URL = 'http://docs.moodle.org/dev/Acceptance_testing'; + /** + * @var Allowed types when using text selectors arguments. + */ + public static $allowedtextselectors = array( + 'css_element' => 'css_element', + 'xpath_element' => 'xpath_element' + ); + + /** + * @var Allowed types when using selector arguments. + */ + public static $allowedselectors = array( + 'link' => 'link', + 'button' => 'button', + 'link_or_button' => 'link_or_button', + 'select' => 'select', + 'checkbox' => 'checkbox', + 'radio' => 'radio', + 'file' => 'file', + 'optgroup' => 'optgroup', + 'option' => 'option', + 'table' => 'table', + 'field' => 'field', + 'fieldset' => 'fieldset', + 'css_element' => 'css_element', + 'xpath_element' => 'xpath_element' + ); + /** * Ensures the behat dir exists in moodledata * @return string Full path diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 9a2c794485f..b073a164045 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -50,7 +50,7 @@ class behat_general extends behat_base { * @Given /^I am on homepage$/ */ public function i_am_on_homepage() { - $this->getSession()->visit($this->locatePath('/')); + $this->getSession()->visit($this->locate_path('/')); } /** @@ -58,6 +58,7 @@ class behat_general extends behat_base { * * @When /^I follow "(?P(?:[^"]|\\")*)"$/ * @throws ElementNotFoundException Thrown by behat_base::find + * @param string $link */ public function click_link($link) { @@ -85,27 +86,39 @@ class behat_general extends behat_base { } /** - * Mouse over a CSS element. + * Generic mouse over action. Mouse over a element of the specified type. * - * @When /^I hover "(?P(?:[^"]|\\")*)"$/ - * @throws ExpectationException Thrown by behat_base::find - * @param string $element + * @When /^I hover "(?P(?:[^"]|\\")*)" "(?P[^"]*)"$/ + * @param string $element Element we look for + * @param string $selectortype The type of what we look for */ - public function i_hover($element) { + public function i_hover($element, $selectortype) { - $exception = new ExpectationException( - 'The hovered element "' . $element . '" was not found anywhere in the page', $this->getSession() - ); - - $node = $this->find('css', $element, $exception); + // Gets the node based on the requested selector type and locator. + $node = $this->get_selected_node($selectortype, $element); $node->mouseOver(); } + /** + * Generic click action. Click on the element of the specified type. + * + * @When /^I click on "(?P(?:[^"]|\\")*)" "(?P[^"]*)"$/ + * @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); + $node->click(); + } + /** * Checks, that page contains specified text. * * @see Behat\MinkExtension\Context\MinkContext * @Then /^I should see "(?P(?:[^"]|\\")*)"$/ + * @param string $text */ public function assert_page_contains_text($text) { $this->assertSession()->pageTextContains($text); @@ -116,40 +129,64 @@ class behat_general extends behat_base { * * @see Behat\MinkExtension\Context\MinkContext * @Then /^I should not see "(?P(?:[^"]|\\")*)"$/ + * @param string $text */ public function assert_page_not_contains_text($text) { $this->assertSession()->pageTextNotContains($text); } /** - * Checks, that element with specified CSS contains specified text. + * Checks, that element with specified CSS selector or XPath contains specified text. * - * @Then /^I should see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" element$/ + * @Then /^I should see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" "(?P[^"]*)"$/ + * @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) { - $this->assertSession()->elementTextContains('css', $element, $text); + public function assert_element_contains_text($text, $element, $selectortype) { + + // Transforming from steps definitions selector/locator format to Mink format. + list($selector, $locator) = $this->transform_selector($selectortype, $element); + + if ($selector != 'css' && $selector != 'xpath') { + throw new ExpectationException('The "' . $selectortype . '" selector can not be used to select text nodes', $this->getSession()); + } + + $this->assertSession()->elementTextContains($selector, $locator, $text); } /** - * Checks, that element with specified CSS doesn't contain specified text. + * Checks, that element with specified CSS selector or XPath doesn't contain specified text. * - * @Then /^I should not see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" element$/ + * @Then /^I should not see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" "(?P[^"]*)"$/ + * @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) { - $this->assertSession()->elementTextNotContains('css', $element, $text); + public function assert_element_not_contains_text($text, $element, $selectortype) { + + // Transforming from steps definitions selector/locator format to mink format. + list($selector, $locator) = $this->transform_selector($selectortype, $element); + + if ($selector != 'css' && $selector != 'xpath') { + throw new ExpectationException('The "' . $selectortype . '" selector can not be used to select text nodes', $this->getSession()); + } + + $this->assertSession()->elementTextNotContains($selector, $locator, $text); } /** - * Checks, that element with given CSS is disabled. + * Checks, that element of specified type is disabled. * - * @Then /^the element "(?P(?:[^"]|\\")*)" should be disabled$/ + * @Then /^the "(?P(?:[^"]|\\")*)" "(?P[^"]*)" should be disabled$/ * @throws ExpectationException Thrown by behat_base::find - * @param string $element + * @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) { + public function the_element_should_be_disabled($element, $selectortype) { - $exception = new ExpectationException('There is no "' . $element . '" element', $this->getSession()); - $node = $this->find('css', $element, $exception); + // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. + $node = $this->get_selected_node($selectortype, $element); if (!$node->hasAttribute('disabled')) { throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession()); @@ -157,16 +194,17 @@ class behat_general extends behat_base { } /** - * Checks, that element with given CSS is enabled. + * Checks, that element of specified type is enabled. * - * @Then /^the element "(?P(?:[^"]|\\")*)" should be enabled$/ + * @Then /^the "(?P(?:[^"]|\\")*)" "(?P[^"]*)" should be enabled$/ * @throws ExpectationException Thrown by behat_base::find - * @param string $element + * @param string $element Element we look on + * @param string $selectortype The type of where we look */ - public function the_element_should_be_enabled($element) { + public function the_element_should_be_enabled($element, $selectortype) { - $exception = new ExpectationException('There is no "' . $element . '" element', $this->getSession()); - $node = $this->find('css', $element, $exception); + // Transforming from steps definitions selector/locator format to mink format and getting the NodeElement. + $node = $this->get_selected_node($selectortype, $element); if ($node->hasAttribute('disabled')) { throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession());