Commit | Line | Data |
---|---|---|
786ea937 DM |
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/>. | |
16 | ||
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 | */ | |
25 | ||
26 | // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. | |
27 | ||
28 | require_once(__DIR__ . '/../../behat/behat_base.php'); | |
29 | ||
ca4f33a7 | 30 | use Behat\Mink\Exception\ExpectationException as ExpectationException, |
d0a9a29b | 31 | Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException, |
bda1dea4 | 32 | Behat\Mink\Exception\DriverException as DriverException, |
39ec8285 DM |
33 | WebDriver\Exception\NoSuchElement as NoSuchElement, |
34 | WebDriver\Exception\StaleElementReference as StaleElementReference; | |
786ea937 DM |
35 | |
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 { | |
50 | ||
51 | /** | |
52 | * Opens Moodle homepage. | |
53 | * | |
786ea937 DM |
54 | * @Given /^I am on homepage$/ |
55 | */ | |
56 | public function i_am_on_homepage() { | |
40923977 | 57 | $this->getSession()->visit($this->locate_path('/')); |
786ea937 DM |
58 | } |
59 | ||
18c84063 DM |
60 | /** |
61 | * Reloads the current page. | |
62 | * | |
63 | * @Given /^I reload the page$/ | |
64 | */ | |
65 | public function reload() { | |
66 | $this->getSession()->reload(); | |
67 | } | |
68 | ||
d0a9a29b DM |
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() { | |
75 | ||
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. | |
fb99ef1d | 80 | return true; |
d0a9a29b DM |
81 | } |
82 | ||
bda1dea4 DM |
83 | // Wrapped in try & catch in case the redirection has already been executed. |
84 | try { | |
85 | $content = $metarefresh->getAttribute('content'); | |
86 | } catch (NoSuchElement $e) { | |
fb99ef1d | 87 | return true; |
39ec8285 | 88 | } catch (StaleElementReference $e) { |
fb99ef1d | 89 | return true; |
bda1dea4 DM |
90 | } |
91 | ||
92 | // Getting the refresh time and the url if present. | |
d0a9a29b DM |
93 | if (strstr($content, 'url') != false) { |
94 | ||
bda1dea4 | 95 | list($waittime, $url) = explode(';', $content); |
d0a9a29b DM |
96 | |
97 | // Cleaning the URL value. | |
98 | $url = trim(substr($url, strpos($url, 'http'))); | |
99 | ||
100 | } else { | |
101 | // Just wait then. | |
102 | $waittime = $content; | |
103 | } | |
104 | ||
105 | ||
106 | // Wait until the URL change is executed. | |
107 | if ($this->running_javascript()) { | |
108 | $this->getSession()->wait($waittime * 1000, false); | |
109 | ||
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); | |
113 | ||
114 | } else { | |
115 | // Reload the page if no URL was provided. | |
116 | $this->getSession()->getDriver()->reload(); | |
117 | } | |
118 | } | |
119 | ||
e5eff0b6 AA |
120 | /** |
121 | * Switches to the specified iframe. | |
122 | * | |
123 | * @Given /^I switch to "(?P<iframe_name_string>(?:[^"]|\\")*)" iframe$/ | |
124 | * @param string $iframename | |
125 | */ | |
126 | public function switch_to_iframe($iframename) { | |
d1e55a47 DM |
127 | |
128 | // We spin to give time to the iframe to be loaded. | |
129 | // Using extended timeout as we don't know about which | |
130 | // kind of iframe will be loaded. | |
131 | $this->spin( | |
132 | function($context, $iframename) { | |
133 | $context->getSession()->switchToIFrame($iframename); | |
134 | ||
135 | // If no exception we are done. | |
136 | return true; | |
137 | }, | |
138 | $iframename, | |
139 | self::EXTENDED_TIMEOUT | |
140 | ); | |
e5eff0b6 AA |
141 | } |
142 | ||
143 | /** | |
144 | * Switches to the main Moodle frame. | |
145 | * | |
146 | * @Given /^I switch to the main frame$/ | |
147 | */ | |
148 | public function switch_to_the_main_frame() { | |
149 | $this->getSession()->switchToIFrame(); | |
150 | } | |
151 | ||
1303eb29 DM |
152 | /** |
153 | * Switches to the specified window. Useful when interacting with popup windows. | |
154 | * | |
155 | * @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/ | |
156 | * @param string $windowname | |
157 | */ | |
158 | public function switch_to_window($windowname) { | |
159 | $this->getSession()->switchToWindow($windowname); | |
160 | } | |
161 | ||
162 | /** | |
163 | * Switches to the main Moodle window. Useful when you finish interacting with popup windows. | |
164 | * | |
165 | * @Given /^I switch to the main window$/ | |
166 | */ | |
167 | public function switch_to_the_main_window() { | |
168 | $this->getSession()->switchToWindow(); | |
169 | } | |
170 | ||
563514b1 | 171 | /** |
7daab401 | 172 | * Accepts the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental. |
563514b1 DM |
173 | * @Given /^I accept the currently displayed dialog$/ |
174 | */ | |
175 | public function accept_currently_displayed_alert_dialog() { | |
176 | $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); | |
177 | } | |
178 | ||
786ea937 DM |
179 | /** |
180 | * Clicks link with specified id|title|alt|text. | |
181 | * | |
786ea937 | 182 | * @When /^I follow "(?P<link_string>(?:[^"]|\\")*)"$/ |
1f9ffbdb | 183 | * @throws ElementNotFoundException Thrown by behat_base::find |
40923977 | 184 | * @param string $link |
786ea937 DM |
185 | */ |
186 | public function click_link($link) { | |
1f9ffbdb DM |
187 | |
188 | $linknode = $this->find_link($link); | |
d1e55a47 | 189 | $this->ensure_node_is_visible($linknode); |
1f9ffbdb | 190 | $linknode->click(); |
786ea937 DM |
191 | } |
192 | ||
193 | /** | |
194 | * Waits X seconds. Required after an action that requires data from an AJAX request. | |
195 | * | |
196 | * @Then /^I wait "(?P<seconds_number>\d+)" seconds$/ | |
197 | * @param int $seconds | |
198 | */ | |
199 | public function i_wait_seconds($seconds) { | |
d0a9a29b DM |
200 | |
201 | if (!$this->running_javascript()) { | |
202 | throw new DriverException('Waits are disabled in scenarios without Javascript support'); | |
203 | } | |
204 | ||
786ea937 DM |
205 | $this->getSession()->wait($seconds * 1000, false); |
206 | } | |
207 | ||
208 | /** | |
209 | * Waits until the page is completely loaded. This step is auto-executed after every step. | |
210 | * | |
211 | * @Given /^I wait until the page is ready$/ | |
212 | */ | |
213 | public function wait_until_the_page_is_ready() { | |
d0a9a29b DM |
214 | |
215 | if (!$this->running_javascript()) { | |
216 | throw new DriverException('Waits are disabled in scenarios without Javascript support'); | |
217 | } | |
218 | ||
d1e55a47 DM |
219 | $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); |
220 | } | |
221 | ||
222 | /** | |
223 | * Waits until the provided element selector exists in the DOM | |
224 | * | |
225 | * Using the protected method as this method will be usually | |
226 | * called by other methods which are not returning a set of | |
227 | * steps and performs the actions directly, so it would not | |
228 | * be executed if it returns another step. | |
229 | ||
230 | * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" exists$/ | |
231 | * @param string $element | |
232 | * @param string $selector | |
233 | * @return void | |
234 | */ | |
235 | public function wait_until_exists($element, $selectortype) { | |
236 | $this->ensure_element_exists($element, $selectortype); | |
237 | } | |
238 | ||
239 | /** | |
240 | * Waits until the provided element does not exist in the DOM | |
241 | * | |
242 | * Using the protected method as this method will be usually | |
243 | * called by other methods which are not returning a set of | |
244 | * steps and performs the actions directly, so it would not | |
245 | * be executed if it returns another step. | |
246 | ||
247 | * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" does not exist$/ | |
248 | * @param string $element | |
249 | * @param string $selector | |
250 | * @return void | |
251 | */ | |
252 | public function wait_until_does_not_exists($element, $selectortype) { | |
253 | $this->ensure_element_does_not_exist($element, $selectortype); | |
786ea937 DM |
254 | } |
255 | ||
256 | /** | |
40923977 | 257 | * Generic mouse over action. Mouse over a element of the specified type. |
786ea937 | 258 | * |
40923977 DM |
259 | * @When /^I hover "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ |
260 | * @param string $element Element we look for | |
261 | * @param string $selectortype The type of what we look for | |
786ea937 | 262 | */ |
40923977 | 263 | public function i_hover($element, $selectortype) { |
1f9ffbdb | 264 | |
40923977 DM |
265 | // Gets the node based on the requested selector type and locator. |
266 | $node = $this->get_selected_node($selectortype, $element); | |
786ea937 DM |
267 | $node->mouseOver(); |
268 | } | |
269 | ||
40923977 DM |
270 | /** |
271 | * Generic click action. Click on the element of the specified type. | |
272 | * | |
273 | * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ | |
274 | * @param string $element Element we look for | |
275 | * @param string $selectortype The type of what we look for | |
276 | */ | |
277 | public function i_click_on($element, $selectortype) { | |
278 | ||
279 | // Gets the node based on the requested selector type and locator. | |
280 | $node = $this->get_selected_node($selectortype, $element); | |
d1e55a47 | 281 | $this->ensure_node_is_visible($node); |
40923977 DM |
282 | $node->click(); |
283 | } | |
284 | ||
072f67fc DM |
285 | /** |
286 | * Click on the element of the specified type which is located inside the second element. | |
287 | * | |
288 | * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ | |
289 | * @param string $element Element we look for | |
290 | * @param string $selectortype The type of what we look for | |
291 | * @param string $nodeelement Element we look in | |
292 | * @param string $nodeselectortype The type of selector where we look in | |
293 | */ | |
294 | public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) { | |
295 | ||
296 | $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); | |
d1e55a47 | 297 | $this->ensure_node_is_visible($node); |
072f67fc DM |
298 | $node->click(); |
299 | } | |
300 | ||
563514b1 | 301 | /** |
7daab401 | 302 | * Drags and drops the specified element to the specified container. This step does not work in all the browsers, consider it experimental. |
563514b1 DM |
303 | * |
304 | * The steps definitions calling this step as part of them should | |
305 | * manage the wait times by themselves as the times and when the | |
306 | * waits should be done depends on what is being dragged & dropper. | |
307 | * | |
308 | * @Given /^I drag "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" and I drop it in "(?P<container_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ | |
309 | * @param string $element | |
310 | * @param string $selectortype | |
311 | * @param string $containerelement | |
312 | * @param string $containerselectortype | |
313 | */ | |
314 | public function i_drag_and_i_drop_it_in($element, $selectortype, $containerelement, $containerselectortype) { | |
315 | ||
316 | list($sourceselector, $sourcelocator) = $this->transform_selector($selectortype, $element); | |
317 | $sourcexpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($sourceselector, $sourcelocator); | |
318 | ||
319 | list($containerselector, $containerlocator) = $this->transform_selector($containerselectortype, $containerelement); | |
320 | $destinationxpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($containerselector, $containerlocator); | |
321 | ||
322 | $this->getSession()->getDriver()->dragTo($sourcexpath, $destinationxpath); | |
323 | } | |
324 | ||
63950e4d DM |
325 | /** |
326 | * Checks, that the specified element is visible. Only available in tests using Javascript. | |
327 | * | |
328 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should be visible$/ | |
329 | * @throws ElementNotFoundException | |
330 | * @throws ExpectationException | |
331 | * @throws DriverException | |
332 | * @param string $element | |
333 | * @param string $selectortype | |
334 | * @return void | |
335 | */ | |
336 | public function should_be_visible($element, $selectortype) { | |
337 | ||
338 | if (!$this->running_javascript()) { | |
339 | throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); | |
340 | } | |
341 | ||
342 | $node = $this->get_selected_node($selectortype, $element); | |
343 | if (!$node->isVisible()) { | |
344 | throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is not visible', $this->getSession()); | |
345 | } | |
346 | } | |
347 | ||
348 | /** | |
74c78e74 | 349 | * Checks, that the existing element is not visible. Only available in tests using Javascript. |
63950e4d | 350 | * |
74c78e74 DM |
351 | * As a "not" method, it's performance could not be good, but in this |
352 | * case the performance is good because the element must exist, | |
353 | * otherwise there would be a ElementNotFoundException, also here we are | |
354 | * not spinning until the element is visible. | |
c1faf86b | 355 | * |
63950e4d DM |
356 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/ |
357 | * @throws ElementNotFoundException | |
358 | * @throws ExpectationException | |
359 | * @param string $element | |
360 | * @param string $selectortype | |
361 | * @return void | |
362 | */ | |
363 | public function should_not_be_visible($element, $selectortype) { | |
364 | ||
365 | try { | |
366 | $this->should_be_visible($element, $selectortype); | |
367 | throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is visible', $this->getSession()); | |
368 | } catch (ExpectationException $e) { | |
369 | // All as expected. | |
370 | } | |
371 | } | |
372 | ||
373 | /** | |
374 | * Checks, that the specified element is visible inside the specified container. Only available in tests using Javascript. | |
375 | * | |
376 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should be visible$/ | |
377 | * @throws ElementNotFoundException | |
378 | * @throws DriverException | |
379 | * @throws ExpectationException | |
380 | * @param string $element Element we look for | |
381 | * @param string $selectortype The type of what we look for | |
382 | * @param string $nodeelement Element we look in | |
383 | * @param string $nodeselectortype The type of selector where we look in | |
384 | */ | |
385 | public function in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { | |
386 | ||
387 | if (!$this->running_javascript()) { | |
388 | throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); | |
389 | } | |
390 | ||
391 | $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); | |
392 | if (!$node->isVisible()) { | |
393 | throw new ExpectationException( | |
394 | '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is not visible', | |
395 | $this->getSession() | |
396 | ); | |
397 | } | |
398 | } | |
399 | ||
400 | /** | |
74c78e74 DM |
401 | * Checks, that the existing element is not visible inside the existing container. Only available in tests using Javascript. |
402 | * | |
403 | * As a "not" method, it's performance could not be good, but in this | |
404 | * case the performance is good because the element must exist, | |
405 | * otherwise there would be a ElementNotFoundException, also here we are | |
406 | * not spinning until the element is visible. | |
63950e4d DM |
407 | * |
408 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should not be visible$/ | |
409 | * @throws ElementNotFoundException | |
410 | * @throws ExpectationException | |
411 | * @param string $element Element we look for | |
412 | * @param string $selectortype The type of what we look for | |
413 | * @param string $nodeelement Element we look in | |
414 | * @param string $nodeselectortype The type of selector where we look in | |
415 | */ | |
416 | public function in_the_should_not_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { | |
417 | ||
418 | try { | |
419 | $this->in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype); | |
420 | throw new ExpectationException( | |
421 | '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is visible', | |
422 | $this->getSession() | |
423 | ); | |
424 | } catch (ExpectationException $e) { | |
425 | // All as expected. | |
426 | } | |
427 | } | |
428 | ||
786ea937 | 429 | /** |
e9af3ed3 | 430 | * Checks, that page contains specified text. It also checks if the text is visible when running Javascript tests. |
786ea937 | 431 | * |
786ea937 | 432 | * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)"$/ |
9a1f4922 | 433 | * @throws ExpectationException |
40923977 | 434 | * @param string $text |
786ea937 DM |
435 | */ |
436 | public function assert_page_contains_text($text) { | |
9a1f4922 | 437 | |
e9af3ed3 DM |
438 | // Looking for all the matching nodes without any other descendant matching the |
439 | // same xpath (we are using contains(., ....). | |
9a1f4922 | 440 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); |
e9af3ed3 DM |
441 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . |
442 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
9a1f4922 | 443 | |
9a1f4922 | 444 | try { |
e9af3ed3 | 445 | $nodes = $this->find_all('xpath', $xpath); |
c1faf86b DM |
446 | } catch (ElementNotFoundException $e) { |
447 | throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession()); | |
448 | } | |
e9af3ed3 | 449 | |
c1faf86b DM |
450 | // If we are not running javascript we have enough with the |
451 | // element existing as we can't check if it is visible. | |
452 | if (!$this->running_javascript()) { | |
453 | return; | |
454 | } | |
455 | ||
456 | // We spin as we don't have enough checking that the element is there, we | |
74c78e74 DM |
457 | // should also ensure that the element is visible. Using microsleep as this |
458 | // is a repeated step and global performance is important. | |
c1faf86b DM |
459 | $this->spin( |
460 | function($context, $args) { | |
461 | ||
462 | foreach ($args['nodes'] as $node) { | |
e9af3ed3 | 463 | if ($node->isVisible()) { |
c1faf86b | 464 | return true; |
e9af3ed3 DM |
465 | } |
466 | } | |
467 | ||
c1faf86b DM |
468 | // If non of the nodes is visible we loop again. |
469 | throw new ExpectationException('"' . $args['text'] . '" text was found but was not visible', $context->getSession()); | |
470 | }, | |
74c78e74 DM |
471 | array('nodes' => $nodes, 'text' => $text), |
472 | false, | |
473 | false, | |
474 | true | |
c1faf86b | 475 | ); |
e9af3ed3 | 476 | |
786ea937 DM |
477 | } |
478 | ||
479 | /** | |
e9af3ed3 | 480 | * Checks, that page doesn't contain specified text. When running Javascript tests it also considers that texts may be hidden. |
786ea937 | 481 | * |
786ea937 | 482 | * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)"$/ |
9a1f4922 | 483 | * @throws ExpectationException |
40923977 | 484 | * @param string $text |
786ea937 DM |
485 | */ |
486 | public function assert_page_not_contains_text($text) { | |
9a1f4922 | 487 | |
c1faf86b DM |
488 | // Looking for all the matching nodes without any other descendant matching the |
489 | // same xpath (we are using contains(., ....). | |
490 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); | |
491 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . | |
492 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
493 | ||
494 | // We should wait a while to ensure that the page is not still loading elements. | |
74c78e74 DM |
495 | // Waiting less than self::TIMEOUT as we already waited for the DOM to be ready and |
496 | // all JS to be executed. | |
9a1f4922 | 497 | try { |
74c78e74 | 498 | $nodes = $this->find_all('xpath', $xpath, false, false, self::REDUCED_TIMEOUT); |
c1faf86b DM |
499 | } catch (ElementNotFoundException $e) { |
500 | // All ok. | |
5458ab3e | 501 | return; |
9a1f4922 | 502 | } |
5458ab3e | 503 | |
c1faf86b DM |
504 | // If we are not running javascript we have enough with the |
505 | // element existing as we can't check if it is hidden. | |
506 | if (!$this->running_javascript()) { | |
507 | throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession()); | |
508 | } | |
509 | ||
510 | // If the element is there we should be sure that it is not visible. | |
511 | $this->spin( | |
512 | function($context, $args) { | |
513 | ||
514 | foreach ($args['nodes'] as $node) { | |
515 | if ($node->isVisible()) { | |
516 | throw new ExpectationException('"' . $args['text'] . '" text was found in the page', $context->getSession()); | |
517 | } | |
518 | } | |
519 | ||
520 | // If non of the found nodes is visible we consider that the text is not visible. | |
521 | return true; | |
522 | }, | |
74c78e74 DM |
523 | array('nodes' => $nodes, 'text' => $text), |
524 | self::REDUCED_TIMEOUT, | |
525 | false, | |
526 | true | |
c1faf86b DM |
527 | ); |
528 | ||
786ea937 DM |
529 | } |
530 | ||
531 | /** | |
e9af3ed3 | 532 | * Checks, that the specified element contains the specified text. When running Javascript tests it also considers that texts may be hidden. |
786ea937 | 533 | * |
40923977 | 534 | * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ |
5458ab3e DM |
535 | * @throws ElementNotFoundException |
536 | * @throws ExpectationException | |
40923977 DM |
537 | * @param string $text |
538 | * @param string $element Element we look in. | |
539 | * @param string $selectortype The type of element where we are looking in. | |
786ea937 | 540 | */ |
40923977 DM |
541 | public function assert_element_contains_text($text, $element, $selectortype) { |
542 | ||
5458ab3e DM |
543 | // Getting the container where the text should be found. |
544 | $container = $this->get_selected_node($selectortype, $element); | |
545 | ||
e9af3ed3 DM |
546 | // Looking for all the matching nodes without any other descendant matching the |
547 | // same xpath (we are using contains(., ....). | |
5458ab3e | 548 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); |
e9af3ed3 DM |
549 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . |
550 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
5458ab3e DM |
551 | |
552 | // Wait until it finds the text inside the container, otherwise custom exception. | |
553 | try { | |
e9af3ed3 | 554 | $nodes = $this->find_all('xpath', $xpath, false, $container); |
c1faf86b DM |
555 | } catch (ElementNotFoundException $e) { |
556 | throw new ExpectationException('"' . $text . '" text was not found in the "' . $element . '" element', $this->getSession()); | |
557 | } | |
e9af3ed3 | 558 | |
c1faf86b DM |
559 | // If we are not running javascript we have enough with the |
560 | // element existing as we can't check if it is visible. | |
561 | if (!$this->running_javascript()) { | |
562 | return; | |
563 | } | |
564 | ||
74c78e74 DM |
565 | // We also check the element visibility when running JS tests. Using microsleep as this |
566 | // is a repeated step and global performance is important. | |
c1faf86b DM |
567 | $this->spin( |
568 | function($context, $args) { | |
569 | ||
570 | foreach ($args['nodes'] as $node) { | |
e9af3ed3 | 571 | if ($node->isVisible()) { |
c1faf86b | 572 | return true; |
e9af3ed3 DM |
573 | } |
574 | } | |
575 | ||
c1faf86b DM |
576 | throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element but was not visible', $context->getSession()); |
577 | }, | |
74c78e74 DM |
578 | array('nodes' => $nodes, 'text' => $text, 'element' => $element), |
579 | false, | |
580 | false, | |
581 | true | |
c1faf86b | 582 | ); |
786ea937 DM |
583 | } |
584 | ||
585 | /** | |
e9af3ed3 | 586 | * Checks, that the specified element does not contain the specified text. When running Javascript tests it also considers that texts may be hidden. |
786ea937 | 587 | * |
40923977 | 588 | * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ |
5458ab3e DM |
589 | * @throws ElementNotFoundException |
590 | * @throws ExpectationException | |
40923977 DM |
591 | * @param string $text |
592 | * @param string $element Element we look in. | |
593 | * @param string $selectortype The type of element where we are looking in. | |
786ea937 | 594 | */ |
40923977 DM |
595 | public function assert_element_not_contains_text($text, $element, $selectortype) { |
596 | ||
c1faf86b DM |
597 | // Getting the container where the text should be found. |
598 | $container = $this->get_selected_node($selectortype, $element); | |
599 | ||
600 | // Looking for all the matching nodes without any other descendant matching the | |
601 | // same xpath (we are using contains(., ....). | |
602 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); | |
603 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . | |
604 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
605 | ||
606 | // We should wait a while to ensure that the page is not still loading elements. | |
607 | // Giving preference to the reliability of the results rather than to the performance. | |
5458ab3e | 608 | try { |
74c78e74 | 609 | $nodes = $this->find_all('xpath', $xpath, false, $container, self::REDUCED_TIMEOUT); |
c1faf86b DM |
610 | } catch (ElementNotFoundException $e) { |
611 | // All ok. | |
5458ab3e DM |
612 | return; |
613 | } | |
614 | ||
c1faf86b DM |
615 | // If we are not running javascript we have enough with the |
616 | // element not being found as we can't check if it is visible. | |
617 | if (!$this->running_javascript()) { | |
618 | throw new ExpectationException('"' . $text . '" text was found in the "' . $element . '" element', $this->getSession()); | |
619 | } | |
620 | ||
621 | // We need to ensure all the found nodes are hidden. | |
622 | $this->spin( | |
623 | function($context, $args) { | |
624 | ||
625 | foreach ($args['nodes'] as $node) { | |
626 | if ($node->isVisible()) { | |
627 | throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element', $context->getSession()); | |
628 | } | |
629 | } | |
630 | ||
631 | // If all the found nodes are hidden we are happy. | |
632 | return true; | |
633 | }, | |
74c78e74 DM |
634 | array('nodes' => $nodes, 'text' => $text, 'element' => $element), |
635 | self::REDUCED_TIMEOUT, | |
636 | false, | |
637 | true | |
c1faf86b | 638 | ); |
786ea937 DM |
639 | } |
640 | ||
60054942 DM |
641 | /** |
642 | * Checks, that the first specified element appears before the second one. | |
643 | * | |
644 | * @Given /^"(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear before "(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ | |
645 | * @throws ExpectationException | |
646 | * @param string $preelement The locator of the preceding element | |
647 | * @param string $preselectortype The locator of the preceding element | |
648 | * @param string $postelement The locator of the latest element | |
649 | * @param string $postselectortype The selector type of the latest element | |
650 | */ | |
651 | public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) { | |
652 | ||
653 | // We allow postselectortype as a non-text based selector. | |
654 | list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); | |
655 | list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); | |
656 | ||
657 | $prexpath = $this->find($preselector, $prelocator)->getXpath(); | |
658 | $postxpath = $this->find($postselector, $postlocator)->getXpath(); | |
659 | ||
660 | // Using following xpath axe to find it. | |
661 | $msg = '"'.$preelement.'" "'.$preselectortype.'" does not appear before "'.$postelement.'" "'.$postselectortype.'"'; | |
662 | $xpath = $prexpath.'/following::*[contains(., '.$postxpath.')]'; | |
663 | if (!$this->getSession()->getDriver()->find($xpath)) { | |
664 | throw new ExpectationException($msg, $this->getSession()); | |
665 | } | |
666 | } | |
667 | ||
668 | /** | |
669 | * Checks, that the first specified element appears after the second one. | |
670 | * | |
671 | * @Given /^"(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear after "(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ | |
672 | * @throws ExpectationException | |
673 | * @param string $postelement The locator of the latest element | |
674 | * @param string $postselectortype The selector type of the latest element | |
675 | * @param string $preelement The locator of the preceding element | |
676 | * @param string $preselectortype The locator of the preceding element | |
677 | */ | |
678 | public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) { | |
679 | ||
680 | // We allow postselectortype as a non-text based selector. | |
681 | list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); | |
682 | list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); | |
683 | ||
684 | $postxpath = $this->find($postselector, $postlocator)->getXpath(); | |
685 | $prexpath = $this->find($preselector, $prelocator)->getXpath(); | |
686 | ||
687 | // Using preceding xpath axe to find it. | |
688 | $msg = '"'.$postelement.'" "'.$postselectortype.'" does not appear after "'.$preelement.'" "'.$preselectortype.'"'; | |
689 | $xpath = $postxpath.'/preceding::*[contains(., '.$prexpath.')]'; | |
690 | if (!$this->getSession()->getDriver()->find($xpath)) { | |
691 | throw new ExpectationException($msg, $this->getSession()); | |
692 | } | |
693 | } | |
694 | ||
786ea937 | 695 | /** |
40923977 | 696 | * Checks, that element of specified type is disabled. |
786ea937 | 697 | * |
40923977 | 698 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be disabled$/ |
1f9ffbdb | 699 | * @throws ExpectationException Thrown by behat_base::find |
40923977 DM |
700 | * @param string $element Element we look in |
701 | * @param string $selectortype The type of element where we are looking in. | |
786ea937 | 702 | */ |
40923977 | 703 | public function the_element_should_be_disabled($element, $selectortype) { |
786ea937 | 704 | |
40923977 DM |
705 | // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. |
706 | $node = $this->get_selected_node($selectortype, $element); | |
786ea937 DM |
707 | |
708 | if (!$node->hasAttribute('disabled')) { | |
709 | throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession()); | |
710 | } | |
711 | } | |
712 | ||
713 | /** | |
40923977 | 714 | * Checks, that element of specified type is enabled. |
786ea937 | 715 | * |
40923977 | 716 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be enabled$/ |
1f9ffbdb | 717 | * @throws ExpectationException Thrown by behat_base::find |
40923977 DM |
718 | * @param string $element Element we look on |
719 | * @param string $selectortype The type of where we look | |
786ea937 | 720 | */ |
40923977 | 721 | public function the_element_should_be_enabled($element, $selectortype) { |
1f9ffbdb | 722 | |
40923977 DM |
723 | // Transforming from steps definitions selector/locator format to mink format and getting the NodeElement. |
724 | $node = $this->get_selected_node($selectortype, $element); | |
786ea937 DM |
725 | |
726 | if ($node->hasAttribute('disabled')) { | |
727 | throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession()); | |
728 | } | |
729 | } | |
730 | ||
a2d3e3b6 MN |
731 | /** |
732 | * Checks the provided element and selector type are readonly on the current page. | |
733 | * | |
734 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be readonly$/ | |
735 | * @throws ExpectationException Thrown by behat_base::find | |
736 | * @param string $element Element we look in | |
737 | * @param string $selectortype The type of element where we are looking in. | |
738 | */ | |
739 | public function the_element_should_be_readonly($element, $selectortype) { | |
740 | // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. | |
741 | $node = $this->get_selected_node($selectortype, $element); | |
742 | ||
743 | if (!$node->hasAttribute('readonly')) { | |
744 | throw new ExpectationException('The element "' . $element . '" is not readonly', $this->getSession()); | |
745 | } | |
746 | } | |
747 | ||
748 | /** | |
749 | * Checks the provided element and selector type are not readonly on the current page. | |
750 | * | |
751 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not be readonly$/ | |
752 | * @throws ExpectationException Thrown by behat_base::find | |
753 | * @param string $element Element we look in | |
754 | * @param string $selectortype The type of element where we are looking in. | |
755 | */ | |
756 | public function the_element_should_not_be_readonly($element, $selectortype) { | |
757 | // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. | |
758 | $node = $this->get_selected_node($selectortype, $element); | |
759 | ||
760 | if ($node->hasAttribute('readonly')) { | |
761 | throw new ExpectationException('The element "' . $element . '" is readonly', $this->getSession()); | |
762 | } | |
763 | } | |
764 | ||
ca4f33a7 | 765 | /** |
62eb5c46 EL |
766 | * Checks the provided element and selector type exists in the current page. |
767 | * | |
768 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
ca4f33a7 | 769 | * |
c51c3b55 | 770 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist$/ |
ca4f33a7 DM |
771 | * @throws ElementNotFoundException Thrown by behat_base::find |
772 | * @param string $element The locator of the specified selector | |
773 | * @param string $selectortype The selector type | |
774 | */ | |
c51c3b55 | 775 | public function should_exist($element, $selectortype) { |
ca4f33a7 DM |
776 | |
777 | // Getting Mink selector and locator. | |
778 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
779 | ||
780 | // Will throw an ElementNotFoundException if it does not exist. | |
781 | $this->find($selector, $locator); | |
782 | } | |
783 | ||
784 | /** | |
62eb5c46 EL |
785 | * Checks that the provided element and selector type not exists in the current page. |
786 | * | |
787 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
ca4f33a7 | 788 | * |
c51c3b55 | 789 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist$/ |
ca4f33a7 DM |
790 | * @throws ExpectationException |
791 | * @param string $element The locator of the specified selector | |
792 | * @param string $selectortype The selector type | |
793 | */ | |
c51c3b55 | 794 | public function should_not_exist($element, $selectortype) { |
ca4f33a7 | 795 | |
74c78e74 DM |
796 | // Getting Mink selector and locator. |
797 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
798 | ||
ca4f33a7 | 799 | try { |
74c78e74 DM |
800 | |
801 | // Using directly the spin method as we want a reduced timeout but there is no | |
802 | // need for a 0.1 seconds interval because in the optimistic case we will timeout. | |
803 | $params = array('selector' => $selector, 'locator' => $locator); | |
804 | // The exception does not really matter as we will catch it and will never "explode". | |
805 | $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $element); | |
806 | ||
807 | // If all goes good it will throw an ElementNotFoundExceptionn that we will catch. | |
808 | $this->spin( | |
809 | function($context, $args) { | |
810 | return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']); | |
811 | }, | |
812 | $params, | |
813 | false, | |
814 | $exception, | |
815 | self::REDUCED_TIMEOUT | |
816 | ); | |
817 | ||
ca4f33a7 | 818 | throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the current page', $this->getSession()); |
62eb5c46 | 819 | } catch (ElementNotFoundException $e) { |
ca4f33a7 DM |
820 | // It passes. |
821 | return; | |
822 | } | |
823 | } | |
824 | ||
066ef320 JM |
825 | /** |
826 | * This step triggers cron like a user would do going to admin/cron.php. | |
827 | * | |
828 | * @Given /^I trigger cron$/ | |
829 | */ | |
830 | public function i_trigger_cron() { | |
831 | $this->getSession()->visit($this->locate_path('/admin/cron.php')); | |
832 | } | |
833 | ||
a2d3e3b6 MN |
834 | /** |
835 | * Checks that an element and selector type exists in another element and selector type on the current page. | |
836 | * | |
837 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
838 | * | |
839 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ | |
840 | * @throws ElementNotFoundException Thrown by behat_base::find | |
841 | * @param string $element The locator of the specified selector | |
842 | * @param string $selectortype The selector type | |
843 | * @param string $containerelement The container selector type | |
844 | * @param string $containerselectortype The container locator | |
845 | */ | |
846 | public function should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { | |
847 | // Get the container node. | |
848 | $containernode = $this->get_selected_node($containerselectortype, $containerelement); | |
849 | ||
850 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
851 | ||
852 | // Specific exception giving info about where can't we find the element. | |
853 | $locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"'; | |
854 | $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg); | |
855 | ||
856 | // Looks for the requested node inside the container node. | |
857 | $this->find($selector, $locator, $exception, $containernode); | |
858 | } | |
859 | ||
860 | /** | |
861 | * Checks that an element and selector type does not exist in another element and selector type on the current page. | |
862 | * | |
863 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
864 | * | |
865 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ | |
866 | * @throws ExpectationException | |
867 | * @param string $element The locator of the specified selector | |
868 | * @param string $selectortype The selector type | |
869 | * @param string $containerelement The container selector type | |
870 | * @param string $containerselectortype The container locator | |
871 | */ | |
872 | public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { | |
74c78e74 DM |
873 | |
874 | // Get the container node; here we throw an exception | |
875 | // if the container node does not exist. | |
876 | $containernode = $this->get_selected_node($containerselectortype, $containerelement); | |
877 | ||
878 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
879 | ||
880 | // Will throw an ElementNotFoundException if it does not exist, but, actually | |
759b323e | 881 | // it should not exist, so we try & catch it. |
a2d3e3b6 | 882 | try { |
74c78e74 DM |
883 | // Would be better to use a 1 second sleep because the element should not be there, |
884 | // but we would need to duplicate the whole find_all() logic to do it, the benefit of | |
885 | // changing to 1 second sleep is not significant. | |
886 | $this->find($selector, $locator, false, $containernode, self::REDUCED_TIMEOUT); | |
a2d3e3b6 MN |
887 | throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the "' . |
888 | $containerelement . '" "' . $containerselectortype . '"', $this->getSession()); | |
889 | } catch (ElementNotFoundException $e) { | |
890 | // It passes. | |
891 | return; | |
892 | } | |
893 | } | |
3b0b5e57 RT |
894 | |
895 | /** | |
896 | * Change browser window size small: 640x480, medium: 1024x768, large: 2560x1600, custom: widthxheight | |
897 | * | |
898 | * Example: I change window size to "small" or I change window size to "1024x768" | |
899 | * | |
900 | * @throws ExpectationException | |
901 | * @Then /^I change window size to "([^"](small|medium|large|\d+x\d+))"$/ | |
902 | * @param string $windowsize size of the window (small|medium|large|wxh). | |
903 | */ | |
904 | public function i_change_window_size_to($windowsize) { | |
905 | $this->resize_window($windowsize); | |
906 | } | |
c0fb7f44 | 907 | |
908 | /** | |
909 | * Checks whether there is an attribute on the given element that contains the specified text. | |
910 | * | |
911 | * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should contain "(?P<text_string>(?:[^"]|\\")*)"$/ | |
912 | * @throws ExpectationException | |
913 | * @param string $attribute Name of attribute | |
914 | * @param string $element The locator of the specified selector | |
915 | * @param string $selectortype The selector type | |
916 | * @param string $text Expected substring | |
917 | */ | |
918 | public function the_attribute_of_should_contain($attribute, $element, $selectortype, $text) { | |
919 | // Get the container node (exception if it doesn't exist). | |
920 | $containernode = $this->get_selected_node($selectortype, $element); | |
921 | $value = $containernode->getAttribute($attribute); | |
922 | if ($value == null) { | |
923 | throw new ExpectationException('The attribute "' . $attribute. '" does not exist', | |
924 | $this->getSession()); | |
925 | } else if (strpos($value, $text) === false) { | |
926 | throw new ExpectationException('The attribute "' . $attribute . | |
927 | '" does not contain "' . $text . '" (actual value: "' . $value . '")', | |
928 | $this->getSession()); | |
929 | } | |
930 | } | |
786ea937 | 931 | } |