Merge branch 'w41_MDL-41220_m26_pluginlist' of https://github.com/skodak/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::*[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         $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text);
308         $xpath = "/descendant::*[not(contains(., $xpathliteral))]";
310         // Wait until it finds the text, otherwise custom exception.
311         try {
312             $this->find('xpath', $xpath);
313         } catch (ElementNotFoundException $e) {
314             throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession());
315         }
316     }
318     /**
319      * Checks, that element with specified CSS selector or XPath contains specified text.
320      *
321      * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/
322      * @param string $text
323      * @param string $element Element we look in.
324      * @param string $selectortype The type of element where we are looking in.
325      */
326     public function assert_element_contains_text($text, $element, $selectortype) {
328         // Transforming from steps definitions selector/locator format to Mink format.
329         list($selector, $locator) = $this->transform_text_selector($selectortype, $element);
330         $this->assertSession()->elementTextContains($selector, $locator, $text);
331     }
333     /**
334      * Checks, that element with specified CSS selector or XPath doesn't contain specified text.
335      *
336      * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/
337      * @param string $text
338      * @param string $element Element we look in.
339      * @param string $selectortype The type of element where we are looking in.
340      */
341     public function assert_element_not_contains_text($text, $element, $selectortype) {
343         // Transforming from steps definitions selector/locator format to mink format.
344         list($selector, $locator) = $this->transform_text_selector($selectortype, $element);
345         $this->assertSession()->elementTextNotContains($selector, $locator, $text);
346     }
348     /**
349      * Checks, that the first specified element appears before the second one.
350      *
351      * @Given /^"(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear before "(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/
352      * @throws ExpectationException
353      * @param string $preelement The locator of the preceding element
354      * @param string $preselectortype The locator of the preceding element
355      * @param string $postelement The locator of the latest element
356      * @param string $postselectortype The selector type of the latest element
357      */
358     public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) {
360         // We allow postselectortype as a non-text based selector.
361         list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement);
362         list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement);
364         $prexpath = $this->find($preselector, $prelocator)->getXpath();
365         $postxpath = $this->find($postselector, $postlocator)->getXpath();
367         // Using following xpath axe to find it.
368         $msg = '"'.$preelement.'" "'.$preselectortype.'" does not appear before "'.$postelement.'" "'.$postselectortype.'"';
369         $xpath = $prexpath.'/following::*[contains(., '.$postxpath.')]';
370         if (!$this->getSession()->getDriver()->find($xpath)) {
371             throw new ExpectationException($msg, $this->getSession());
372         }
373     }
375     /**
376      * Checks, that the first specified element appears after the second one.
377      *
378      * @Given /^"(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear after "(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/
379      * @throws ExpectationException
380      * @param string $postelement The locator of the latest element
381      * @param string $postselectortype The selector type of the latest element
382      * @param string $preelement The locator of the preceding element
383      * @param string $preselectortype The locator of the preceding element
384      */
385     public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) {
387         // We allow postselectortype as a non-text based selector.
388         list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement);
389         list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement);
391         $postxpath = $this->find($postselector, $postlocator)->getXpath();
392         $prexpath = $this->find($preselector, $prelocator)->getXpath();
394         // Using preceding xpath axe to find it.
395         $msg = '"'.$postelement.'" "'.$postselectortype.'" does not appear after "'.$preelement.'" "'.$preselectortype.'"';
396         $xpath = $postxpath.'/preceding::*[contains(., '.$prexpath.')]';
397         if (!$this->getSession()->getDriver()->find($xpath)) {
398             throw new ExpectationException($msg, $this->getSession());
399         }
400     }
402     /**
403      * Checks, that element of specified type is disabled.
404      *
405      * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be disabled$/
406      * @throws ExpectationException Thrown by behat_base::find
407      * @param string $element Element we look in
408      * @param string $selectortype The type of element where we are looking in.
409      */
410     public function the_element_should_be_disabled($element, $selectortype) {
412         // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement.
413         $node = $this->get_selected_node($selectortype, $element);
415         if (!$node->hasAttribute('disabled')) {
416             throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession());
417         }
418     }
420     /**
421      * Checks, that element of specified type is enabled.
422      *
423      * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be enabled$/
424      * @throws ExpectationException Thrown by behat_base::find
425      * @param string $element Element we look on
426      * @param string $selectortype The type of where we look
427      */
428     public function the_element_should_be_enabled($element, $selectortype) {
430         // Transforming from steps definitions selector/locator format to mink format and getting the NodeElement.
431         $node = $this->get_selected_node($selectortype, $element);
433         if ($node->hasAttribute('disabled')) {
434             throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession());
435         }
436     }
438     /**
439      * Checks the provided element and selector type exists in the current page.
440      *
441      * This step is for advanced users, use it if you don't find anything else suitable for what you need.
442      *
443      * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exists$/
444      * @throws ElementNotFoundException Thrown by behat_base::find
445      * @param string $element The locator of the specified selector
446      * @param string $selectortype The selector type
447      */
448     public function should_exists($element, $selectortype) {
450         // Getting Mink selector and locator.
451         list($selector, $locator) = $this->transform_selector($selectortype, $element);
453         // Will throw an ElementNotFoundException if it does not exist.
454         $this->find($selector, $locator);
455     }
457     /**
458      * Checks that the provided element and selector type not exists in the current page.
459      *
460      * This step is for advanced users, use it if you don't find anything else suitable for what you need.
461      *
462      * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exists$/
463      * @throws ExpectationException
464      * @param string $element The locator of the specified selector
465      * @param string $selectortype The selector type
466      */
467     public function should_not_exists($element, $selectortype) {
469         try {
470             $this->should_exists($element, $selectortype);
471             throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the current page', $this->getSession());
472         } catch (ElementNotFoundException $e) {
473             // It passes.
474             return;
475         }
476     }