diff --git a/lib/behat/behat_base.php b/lib/behat/behat_base.php index 2e8a31eb359..c957837f8c4 100644 --- a/lib/behat/behat_base.php +++ b/lib/behat/behat_base.php @@ -48,4 +48,84 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext { */ const TIMEOUT = 6; + /** + * Returns fixed step argument (with \\" replaced back to "). + * + * \\ is the chars combination to add when you + * want to escape the " character that is used as var + * delimiter. + * + * @see Behat\MinkExtension\Context\MinkContext + * @param string $argument + * @return string + */ + protected function fixStepArgument($argument) { + return str_replace('\\"', '"', $argument); + } + + /** + * Locates url, based on provided path. + * Override to provide custom routing mechanism. + * + * @see Behat\MinkExtension\Context\MinkContext + * @param string $path + * @return string + */ + protected function locatePath($path) { + $startUrl = rtrim($this->getMinkParameter('base_url'), '/') . '/'; + return 0 !== strpos($path, 'http') ? $startUrl . ltrim($path, '/') : $path; + } + + /** + * Executes the passed closure until returns true or time outs. + * + * In most cases the document.readyState === 'complete' will be enough, but sometimes JS + * requires more time to be completely loaded or an element to be visible or whatever is required to + * perform some action on an element; this method receives a closure which should contain the + * required statements to ensure the step definition actions and assertions have all their needs + * satisfied and executes it until they are satisfied or it timeouts. Redirects the return of the + * closure to the caller. + * + * The closures requirements to work well with this spin method are: + * - Must return false, null or '' if something goes wrong + * - Must return something != false if finishes as expected, this will be the (mixed) value + * returned by spin() + * + * Requires the exception to provide more accurate feedback to tests writers. + * + * @throws Exception If it timeouts without receiving something != false from the closure + * @param Closure $lambda The function to execute. + * @param Exception $exception The exception to throw in case it time outs. + * @param array $args Arguments to pass to the closure + * @return mixed The value returned by the closure + */ + protected function spin($lambda, $exception, $args, $timeout = false) { + + // Using default timeout which is pretty high. + if (!$timeout) { + $timeout = self::TIMEOUT; + } + + for ($i = 0; $i < $timeout; $i++) { + + // We catch the exception thrown by the step definition to execute it again. + try { + + // We don't check with !== because most of the time closures will return + // direct Behat methods returns and we are not sure it will be always (bool)false. + if ($return = $lambda($this, $args)) { + return $return; + } + } catch(Exception $e) { + // We wait until no exception is thrown or timeout expires. + continue; + } + + sleep(1); + } + + // Throwing exception to the user. + throw $exception; + } + } diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php new file mode 100644 index 00000000000..0b178b957ee --- /dev/null +++ b/lib/tests/behat/behat_general.php @@ -0,0 +1,184 @@ +. + +/** + * 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\Mink\Exception\ExpectationException as ExpectationException; + +/** + * 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 { + + /** + * Opens Moodle homepage. + * + * @see Behat\MinkExtension\Context\MinkContext + * @Given /^I am on homepage$/ + */ + public function i_am_on_homepage() { + $this->getSession()->visit($this->locatePath('/')); + } + + /** + * Clicks link with specified id|title|alt|text. + * + * @see Behat\MinkExtension\Context\MinkContext + * @When /^I follow "(?P(?:[^"]|\\")*)"$/ + */ + public function click_link($link) { + $link = $this->fixStepArgument($link); + $this->getSession()->getPage()->clickLink($link); + } + + /** + * Waits X seconds. Required after an action that requires data from an AJAX request. + * + * @Then /^I wait "(?P\d+)" seconds$/ + * @param int $seconds + */ + public function i_wait_seconds($seconds) { + $this->getSession()->wait($seconds * 1000, false); + } + + /** + * 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() { + $this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")'); + } + + /** + * Mouse over a CSS element. + * + * @throws ExpectationException + * @see Sanpi/Behatch/Context/BrowserContext.php + * @When /^I hover "(?P(?:[^"]|\\")*)"$/ + * @param string $element + */ + public function i_hover($element) { + $node = $this->getSession()->getPage()->find('css', $element); + if ($node === null) { + throw new ExpectationException('The hovered element "' . $element . '" was not found anywhere in the page', $this->getSession()); + } + $node->mouseOver(); + } + + /** + * Checks, that page contains specified text. + * + * @see Behat\MinkExtension\Context\MinkContext + * @Then /^I should see "(?P(?:[^"]|\\")*)"$/ + */ + public function assert_page_contains_text($text) { + $this->assertSession()->pageTextContains($this->fixStepArgument($text)); + } + + /** + * Checks, that page doesn't contain specified text. + * + * @see Behat\MinkExtension\Context\MinkContext + * @Then /^I should not see "(?P(?:[^"]|\\")*)"$/ + */ + public function assert_page_not_contains_text($text) { + $this->assertSession()->pageTextNotContains($this->fixStepArgument($text)); + } + + /** + * Checks, that element with specified CSS contains specified text. + * + * @Then /^I should see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" element$/ + */ + public function assert_element_contains_text($element, $text) { + $element = $this->fixStepArgument($element); + $this->assertSession()->elementTextContains('css', $element, $this->fixStepArgument($text)); + } + + /** + * Checks, that element with specified CSS doesn't contain specified text. + * + * @Then /^I should not see "(?P(?:[^"]|\\")*)" in the "(?P(?:[^"]|\\")*)" element$/ + */ + public function assert_element_not_contains_text($element, $text) { + $element = $this->fixStepArgument($element); + $this->assertSession()->elementTextNotContains('css', $element, $this->fixStepArgument($text)); + } + + /** + * Checks, that element with given CSS is disabled. + * + * @throws ExpectationException + * @see Sanpi/Behatch/Context/BrowserContext + * @Then /^the element "(?P(?:[^"]|\\")*)" should be disabled$/ + * @param string $element + */ + public function the_element_should_be_disabled($element) { + + $element = $this->fixStepArgument($element); + + $node = $this->getSession()->getPage()->find('css', $element); + if ($node == null) { + throw new ExpectationException('There is no "' . $element . '" element', $this->getSession()); + } + + if (!$node->hasAttribute('disabled')) { + throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession()); + } + } + + /** + * Checks, that element with given CSS is enabled. + * + * @throws ExpectationException + * @see Sanpi/Behatch/Context/BrowserContext.php + * @Then /^the element "(?P(?:[^"]|\\")*)" should be enabled$/ + * @param string $element + */ + public function the_element_should_be_enabled($element) { + $node = $this->getSession()->getPage()->find('css', $element); + if ($node == null) { + throw new ExpectationException('There is no "' . $element . '" element', $this->getSession()); + } + + if ($node->hasAttribute('disabled')) { + throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession()); + } + } + +}