Merge branch 'MDL-42013_master' of git://github.com/dmonllao/moodle
[moodle.git] / lib / tests / behat / behat_general.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * General use steps definitions.
19  *
20  * @package   core
21  * @category  test
22  * @copyright 2012 David MonllaĆ³
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
28 require_once(__DIR__ . '/../../behat/behat_base.php');
30 use Behat\Mink\Exception\ExpectationException as ExpectationException,
31     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
32     Behat\Mink\Exception\DriverException as DriverException,
33     WebDriver\Exception\NoSuchElement as NoSuchElement,
34     WebDriver\Exception\StaleElementReference as StaleElementReference;
36 /**
37  * Cross component steps definitions.
38  *
39  * Basic web application definitions from MinkExtension and
40  * BehatchExtension. Definitions modified according to our needs
41  * when necessary and including only the ones we need to avoid
42  * overlapping and confusion.
43  *
44  * @package   core
45  * @category  test
46  * @copyright 2012 David MonllaĆ³
47  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
48  */
49 class behat_general extends behat_base {
51     /**
52      * Opens Moodle homepage.
53      *
54      * @Given /^I am on homepage$/
55      */
56     public function i_am_on_homepage() {
57         $this->getSession()->visit($this->locate_path('/'));
58     }
60     /**
61      * Reloads the current page.
62      *
63      * @Given /^I reload the page$/
64      */
65     public function reload() {
66         $this->getSession()->reload();
67     }
69     /**
70      * Follows the page redirection. Use this step after any action that shows a message and waits for a redirection
71      *
72      * @Given /^I wait to be redirected$/
73      */
74     public function i_wait_to_be_redirected() {
76         // Xpath and processes based on core_renderer::redirect_message(), core_renderer::$metarefreshtag and
77         // moodle_page::$periodicrefreshdelay possible values.
78         if (!$metarefresh = $this->getSession()->getPage()->find('xpath', "//head/descendant::meta[@http-equiv='refresh']")) {
79             // We don't fail the scenario if no redirection with message is found to avoid race condition false failures.
80             return false;
81         }
83         // Wrapped in try & catch in case the redirection has already been executed.
84         try {
85             $content = $metarefresh->getAttribute('content');
86         } catch (NoSuchElement $e) {
87             return false;
88         } catch (StaleElementReference $e) {
89             return false;
90         }
92         // Getting the refresh time and the url if present.
93         if (strstr($content, 'url') != false) {
95             list($waittime, $url) = explode(';', $content);
97             // Cleaning the URL value.
98             $url = trim(substr($url, strpos($url, 'http')));
100         } else {
101             // Just wait then.
102             $waittime = $content;
103         }
106         // Wait until the URL change is executed.
107         if ($this->running_javascript()) {
108             $this->getSession()->wait($waittime * 1000, false);
110         } else if (!empty($url)) {
111             // We redirect directly as we can not wait for an automatic redirection.
112             $this->getSession()->getDriver()->getClient()->request('get', $url);
114         } else {
115             // Reload the page if no URL was provided.
116             $this->getSession()->getDriver()->reload();
117         }
118     }
120     /**
121      * Switches to the specified window. Useful when interacting with popup windows.
122      *
123      * @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/
124      * @param string $windowname
125      */
126     public function switch_to_window($windowname) {
127         $this->getSession()->switchToWindow($windowname);
128     }
130     /**
131      * Switches to the main Moodle window. Useful when you finish interacting with popup windows.
132      *
133      * @Given /^I switch to the main window$/
134      */
135     public function switch_to_the_main_window() {
136         $this->getSession()->switchToWindow();
137     }
139     /**
140      * Accepts the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental.
141      * @Given /^I accept the currently displayed dialog$/
142      */
143     public function accept_currently_displayed_alert_dialog() {
144         $this->getSession()->getDriver()->getWebDriverSession()->accept_alert();
145     }
147     /**
148      * Clicks link with specified id|title|alt|text.
149      *
150      * @When /^I follow "(?P<link_string>(?:[^"]|\\")*)"$/
151      * @throws ElementNotFoundException Thrown by behat_base::find
152      * @param string $link
153      */
154     public function click_link($link) {
156         $linknode = $this->find_link($link);
157         $linknode->click();
158     }
160     /**
161      * Waits X seconds. Required after an action that requires data from an AJAX request.
162      *
163      * @Then /^I wait "(?P<seconds_number>\d+)" seconds$/
164      * @param int $seconds
165      */
166     public function i_wait_seconds($seconds) {
168         if (!$this->running_javascript()) {
169             throw new DriverException('Waits are disabled in scenarios without Javascript support');
170         }
172         $this->getSession()->wait($seconds * 1000, false);
173     }
175     /**
176      * Waits until the page is completely loaded. This step is auto-executed after every step.
177      *
178      * @Given /^I wait until the page is ready$/
179      */
180     public function wait_until_the_page_is_ready() {
182         if (!$this->running_javascript()) {
183             throw new DriverException('Waits are disabled in scenarios without Javascript support');
184         }
186         $this->getSession()->wait(self::TIMEOUT, '(document.readyState === "complete")');
187     }
189     /**
190      * Generic mouse over action. Mouse over a element of the specified type.
191      *
192      * @When /^I hover "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/
193      * @param string $element Element we look for
194      * @param string $selectortype The type of what we look for
195      */
196     public function i_hover($element, $selectortype) {
198         // Gets the node based on the requested selector type and locator.
199         $node = $this->get_selected_node($selectortype, $element);
200         $node->mouseOver();
201     }
203     /**
204      * Generic click action. Click on the element of the specified type.
205      *
206      * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/
207      * @param string $element Element we look for
208      * @param string $selectortype The type of what we look for
209      */
210     public function i_click_on($element, $selectortype) {
212         // Gets the node based on the requested selector type and locator.
213         $node = $this->get_selected_node($selectortype, $element);
214         $node->click();
215     }
217     /**
218      * Click on the element of the specified type which is located inside the second element.
219      *
220      * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/
221      * @param string $element Element we look for
222      * @param string $selectortype The type of what we look for
223      * @param string $nodeelement Element we look in
224      * @param string $nodeselectortype The type of selector where we look in
225      */
226     public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) {
228         $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement);
229         $node->click();
230     }
232     /**
233      * Click on the specified element inside a table row containing the specified text.
234      *
235      * @Given /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" in the "(?P<row_text_string>(?:[^"]|\\")*)" table row$/
236      * @throws ElementNotFoundException
237      * @param string $element Element we look for
238      * @param string $selectortype The type of what we look for
239      * @param string $tablerowtext The table row text
240      */
241     public function i_click_on_in_the_table_row($element, $selectortype, $tablerowtext) {
243         // The table row container.
244         $nocontainerexception = new ElementNotFoundException($this->getSession(), '"' . $tablerowtext . '" row text ');
245         $tablerowtext = $this->getSession()->getSelectorsHandler()->xpathLiteral($tablerowtext);
246         $rownode = $this->find('xpath', "//tr[contains(., $tablerowtext)]", $nocontainerexception);
248         // Looking for the element DOM node inside the specified row.
249         list($selector, $locator) = $this->transform_selector($selectortype, $element);
250         $elementnode = $this->find($selector, $locator, false, $rownode);
251         $elementnode->click();
252     }
254     /**
255      * Drags and drops the specified element to the specified container. This step does not work in all the browsers, consider it experimental.
256      *
257      * The steps definitions calling this step as part of them should
258      * manage the wait times by themselves as the times and when the
259      * waits should be done depends on what is being dragged & dropper.
260      *
261      * @Given /^I drag "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" and I drop it in "(?P<container_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/
262      * @param string $element
263      * @param string $selectortype
264      * @param string $containerelement
265      * @param string $containerselectortype
266      */
267     public function i_drag_and_i_drop_it_in($element, $selectortype, $containerelement, $containerselectortype) {
269         list($sourceselector, $sourcelocator) = $this->transform_selector($selectortype, $element);
270         $sourcexpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($sourceselector, $sourcelocator);
272         list($containerselector, $containerlocator) = $this->transform_selector($containerselectortype, $containerelement);
273         $destinationxpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($containerselector, $containerlocator);
275         $this->getSession()->getDriver()->dragTo($sourcexpath, $destinationxpath);
276     }
278     /**
279      * Checks, that page contains specified text.
280      *
281      * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)"$/
282      * @throws ExpectationException
283      * @param string $text
284      */
285     public function assert_page_contains_text($text) {
287         $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
288         $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]";
290         // Wait until it finds the text, otherwise custom exception.
291         try {
292             $this->find('xpath', $xpath);
293         } catch (ElementNotFoundException $e) {
294             throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession());
295         }
296     }
298     /**
299      * Checks, that page doesn't contain specified text.
300      *
301      * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)"$/
302      * @throws ExpectationException
303      * @param string $text
304      */
305     public function assert_page_not_contains_text($text) {
307         // Delegating the process to assert_page_contains_text.
308         try {
309             $this->assert_page_contains_text($text);
310         } catch (ExpectationException $e) {
311             // It should not appear, so this is good.
312             return;
313         }
315         // If the page contains the text this is failing.
316         throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession());
317     }
319     /**
320      * Checks, that element with specified CSS selector or XPath contains specified text.
321      *
322      * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/
323      * @throws ElementNotFoundException
324      * @throws ExpectationException
325      * @param string $text
326      * @param string $element Element we look in.
327      * @param string $selectortype The type of element where we are looking in.
328      */
329     public function assert_element_contains_text($text, $element, $selectortype) {
331         // Getting the container where the text should be found.
332         $container = $this->get_selected_node($selectortype, $element);
334         $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
335         $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]";
337         // Wait until it finds the text inside the container, otherwise custom exception.
338         try {
339             $this->find('xpath', $xpath, false, $container);
340         } catch (ElementNotFoundException $e) {
341             throw new ExpectationException('"' . $text . '" text was not found in the ' . $element . ' element', $this->getSession());
342         }
344     }
346     /**
347      * Checks, that element with specified CSS selector or XPath doesn't contain specified text.
348      *
349      * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/
350      * @throws ElementNotFoundException
351      * @throws ExpectationException
352      * @param string $text
353      * @param string $element Element we look in.
354      * @param string $selectortype The type of element where we are looking in.
355      */
356     public function assert_element_not_contains_text($text, $element, $selectortype) {
358         // Delegating the process to assert_element_contains_text.
359         try {
360             $this->assert_element_contains_text($text, $element, $selectortype);
361         } catch (ExpectationException $e) {
362             // It should not appear, so this is good.
363             // We only catch ExpectationException as ElementNotFoundException
364             // will be thrown if the container does not exist.
365             return;
366         }
368         // If the element contains the text this is failing.
369         throw new ExpectationException('"' . $text . '" text was found in the ' . $element . ' element', $this->getSession());
370     }
372     /**
373      * Checks, that the first specified element appears before the second one.
374      *
375      * @Given /^"(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear before "(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/
376      * @throws ExpectationException
377      * @param string $preelement The locator of the preceding element
378      * @param string $preselectortype The locator of the preceding element
379      * @param string $postelement The locator of the latest element
380      * @param string $postselectortype The selector type of the latest element
381      */
382     public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) {
384         // We allow postselectortype as a non-text based selector.
385         list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement);
386         list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement);
388         $prexpath = $this->find($preselector, $prelocator)->getXpath();
389         $postxpath = $this->find($postselector, $postlocator)->getXpath();
391         // Using following xpath axe to find it.
392         $msg = '"'.$preelement.'" "'.$preselectortype.'" does not appear before "'.$postelement.'" "'.$postselectortype.'"';
393         $xpath = $prexpath.'/following::*[contains(., '.$postxpath.')]';
394         if (!$this->getSession()->getDriver()->find($xpath)) {
395             throw new ExpectationException($msg, $this->getSession());
396         }
397     }
399     /**
400      * Checks, that the first specified element appears after the second one.
401      *
402      * @Given /^"(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear after "(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/
403      * @throws ExpectationException
404      * @param string $postelement The locator of the latest element
405      * @param string $postselectortype The selector type of the latest element
406      * @param string $preelement The locator of the preceding element
407      * @param string $preselectortype The locator of the preceding element
408      */
409     public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) {
411         // We allow postselectortype as a non-text based selector.
412         list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement);
413         list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement);
415         $postxpath = $this->find($postselector, $postlocator)->getXpath();
416         $prexpath = $this->find($preselector, $prelocator)->getXpath();
418         // Using preceding xpath axe to find it.
419         $msg = '"'.$postelement.'" "'.$postselectortype.'" does not appear after "'.$preelement.'" "'.$preselectortype.'"';
420         $xpath = $postxpath.'/preceding::*[contains(., '.$prexpath.')]';
421         if (!$this->getSession()->getDriver()->find($xpath)) {
422             throw new ExpectationException($msg, $this->getSession());
423         }
424     }
426     /**
427      * Checks, that element of specified type is disabled.
428      *
429      * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be disabled$/
430      * @throws ExpectationException Thrown by behat_base::find
431      * @param string $element Element we look in
432      * @param string $selectortype The type of element where we are looking in.
433      */
434     public function the_element_should_be_disabled($element, $selectortype) {
436         // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement.
437         $node = $this->get_selected_node($selectortype, $element);
439         if (!$node->hasAttribute('disabled')) {
440             throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession());
441         }
442     }
444     /**
445      * Checks, that element of specified type is enabled.
446      *
447      * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be enabled$/
448      * @throws ExpectationException Thrown by behat_base::find
449      * @param string $element Element we look on
450      * @param string $selectortype The type of where we look
451      */
452     public function the_element_should_be_enabled($element, $selectortype) {
454         // Transforming from steps definitions selector/locator format to mink format and getting the NodeElement.
455         $node = $this->get_selected_node($selectortype, $element);
457         if ($node->hasAttribute('disabled')) {
458             throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession());
459         }
460     }
462     /**
463      * Checks the provided element and selector type exists in the current page.
464      *
465      * This step is for advanced users, use it if you don't find anything else suitable for what you need.
466      *
467      * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exists$/
468      * @throws ElementNotFoundException Thrown by behat_base::find
469      * @param string $element The locator of the specified selector
470      * @param string $selectortype The selector type
471      */
472     public function should_exists($element, $selectortype) {
474         // Getting Mink selector and locator.
475         list($selector, $locator) = $this->transform_selector($selectortype, $element);
477         // Will throw an ElementNotFoundException if it does not exist.
478         $this->find($selector, $locator);
479     }
481     /**
482      * Checks that the provided element and selector type not exists in the current page.
483      *
484      * This step is for advanced users, use it if you don't find anything else suitable for what you need.
485      *
486      * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exists$/
487      * @throws ExpectationException
488      * @param string $element The locator of the specified selector
489      * @param string $selectortype The selector type
490      */
491     public function should_not_exists($element, $selectortype) {
493         try {
494             $this->should_exists($element, $selectortype);
495             throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the current page', $this->getSession());
496         } catch (ElementNotFoundException $e) {
497             // It passes.
498             return;
499         }
500     }