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 | 33 | WebDriver\Exception\NoSuchElement as NoSuchElement, |
641459a8 RT |
34 | WebDriver\Exception\StaleElementReference as StaleElementReference, |
35 | Behat\Gherkin\Node\TableNode as TableNode; | |
786ea937 DM |
36 | |
37 | /** | |
38 | * Cross component steps definitions. | |
39 | * | |
40 | * Basic web application definitions from MinkExtension and | |
41 | * BehatchExtension. Definitions modified according to our needs | |
42 | * when necessary and including only the ones we need to avoid | |
43 | * overlapping and confusion. | |
44 | * | |
45 | * @package core | |
46 | * @category test | |
47 | * @copyright 2012 David Monllaó | |
48 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
49 | */ | |
50 | class behat_general extends behat_base { | |
51 | ||
52 | /** | |
53 | * Opens Moodle homepage. | |
54 | * | |
786ea937 DM |
55 | * @Given /^I am on homepage$/ |
56 | */ | |
57 | public function i_am_on_homepage() { | |
40923977 | 58 | $this->getSession()->visit($this->locate_path('/')); |
786ea937 DM |
59 | } |
60 | ||
18c84063 DM |
61 | /** |
62 | * Reloads the current page. | |
63 | * | |
64 | * @Given /^I reload the page$/ | |
65 | */ | |
66 | public function reload() { | |
67 | $this->getSession()->reload(); | |
68 | } | |
69 | ||
d0a9a29b DM |
70 | /** |
71 | * Follows the page redirection. Use this step after any action that shows a message and waits for a redirection | |
72 | * | |
73 | * @Given /^I wait to be redirected$/ | |
74 | */ | |
75 | public function i_wait_to_be_redirected() { | |
76 | ||
77 | // Xpath and processes based on core_renderer::redirect_message(), core_renderer::$metarefreshtag and | |
78 | // moodle_page::$periodicrefreshdelay possible values. | |
79 | if (!$metarefresh = $this->getSession()->getPage()->find('xpath', "//head/descendant::meta[@http-equiv='refresh']")) { | |
80 | // We don't fail the scenario if no redirection with message is found to avoid race condition false failures. | |
fb99ef1d | 81 | return true; |
d0a9a29b DM |
82 | } |
83 | ||
bda1dea4 DM |
84 | // Wrapped in try & catch in case the redirection has already been executed. |
85 | try { | |
86 | $content = $metarefresh->getAttribute('content'); | |
87 | } catch (NoSuchElement $e) { | |
fb99ef1d | 88 | return true; |
39ec8285 | 89 | } catch (StaleElementReference $e) { |
fb99ef1d | 90 | return true; |
bda1dea4 DM |
91 | } |
92 | ||
93 | // Getting the refresh time and the url if present. | |
d0a9a29b DM |
94 | if (strstr($content, 'url') != false) { |
95 | ||
bda1dea4 | 96 | list($waittime, $url) = explode(';', $content); |
d0a9a29b DM |
97 | |
98 | // Cleaning the URL value. | |
99 | $url = trim(substr($url, strpos($url, 'http'))); | |
100 | ||
101 | } else { | |
102 | // Just wait then. | |
103 | $waittime = $content; | |
104 | } | |
105 | ||
106 | ||
107 | // Wait until the URL change is executed. | |
108 | if ($this->running_javascript()) { | |
109 | $this->getSession()->wait($waittime * 1000, false); | |
110 | ||
111 | } else if (!empty($url)) { | |
112 | // We redirect directly as we can not wait for an automatic redirection. | |
113 | $this->getSession()->getDriver()->getClient()->request('get', $url); | |
114 | ||
115 | } else { | |
116 | // Reload the page if no URL was provided. | |
117 | $this->getSession()->getDriver()->reload(); | |
118 | } | |
119 | } | |
120 | ||
e5eff0b6 AA |
121 | /** |
122 | * Switches to the specified iframe. | |
123 | * | |
124 | * @Given /^I switch to "(?P<iframe_name_string>(?:[^"]|\\")*)" iframe$/ | |
125 | * @param string $iframename | |
126 | */ | |
127 | public function switch_to_iframe($iframename) { | |
d1e55a47 DM |
128 | |
129 | // We spin to give time to the iframe to be loaded. | |
130 | // Using extended timeout as we don't know about which | |
131 | // kind of iframe will be loaded. | |
132 | $this->spin( | |
133 | function($context, $iframename) { | |
134 | $context->getSession()->switchToIFrame($iframename); | |
135 | ||
136 | // If no exception we are done. | |
137 | return true; | |
138 | }, | |
139 | $iframename, | |
140 | self::EXTENDED_TIMEOUT | |
141 | ); | |
e5eff0b6 AA |
142 | } |
143 | ||
144 | /** | |
145 | * Switches to the main Moodle frame. | |
146 | * | |
147 | * @Given /^I switch to the main frame$/ | |
148 | */ | |
149 | public function switch_to_the_main_frame() { | |
150 | $this->getSession()->switchToIFrame(); | |
151 | } | |
152 | ||
1303eb29 DM |
153 | /** |
154 | * Switches to the specified window. Useful when interacting with popup windows. | |
155 | * | |
156 | * @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/ | |
157 | * @param string $windowname | |
158 | */ | |
159 | public function switch_to_window($windowname) { | |
160 | $this->getSession()->switchToWindow($windowname); | |
161 | } | |
162 | ||
163 | /** | |
164 | * Switches to the main Moodle window. Useful when you finish interacting with popup windows. | |
165 | * | |
166 | * @Given /^I switch to the main window$/ | |
167 | */ | |
168 | public function switch_to_the_main_window() { | |
169 | $this->getSession()->switchToWindow(); | |
170 | } | |
171 | ||
563514b1 | 172 | /** |
7daab401 | 173 | * Accepts the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental. |
563514b1 DM |
174 | * @Given /^I accept the currently displayed dialog$/ |
175 | */ | |
176 | public function accept_currently_displayed_alert_dialog() { | |
177 | $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); | |
178 | } | |
179 | ||
786ea937 DM |
180 | /** |
181 | * Clicks link with specified id|title|alt|text. | |
182 | * | |
786ea937 | 183 | * @When /^I follow "(?P<link_string>(?:[^"]|\\")*)"$/ |
1f9ffbdb | 184 | * @throws ElementNotFoundException Thrown by behat_base::find |
40923977 | 185 | * @param string $link |
786ea937 DM |
186 | */ |
187 | public function click_link($link) { | |
1f9ffbdb DM |
188 | |
189 | $linknode = $this->find_link($link); | |
d1e55a47 | 190 | $this->ensure_node_is_visible($linknode); |
1f9ffbdb | 191 | $linknode->click(); |
786ea937 DM |
192 | } |
193 | ||
194 | /** | |
195 | * Waits X seconds. Required after an action that requires data from an AJAX request. | |
196 | * | |
197 | * @Then /^I wait "(?P<seconds_number>\d+)" seconds$/ | |
198 | * @param int $seconds | |
199 | */ | |
200 | public function i_wait_seconds($seconds) { | |
d0a9a29b DM |
201 | |
202 | if (!$this->running_javascript()) { | |
203 | throw new DriverException('Waits are disabled in scenarios without Javascript support'); | |
204 | } | |
205 | ||
786ea937 DM |
206 | $this->getSession()->wait($seconds * 1000, false); |
207 | } | |
208 | ||
209 | /** | |
210 | * Waits until the page is completely loaded. This step is auto-executed after every step. | |
211 | * | |
212 | * @Given /^I wait until the page is ready$/ | |
213 | */ | |
214 | public function wait_until_the_page_is_ready() { | |
d0a9a29b DM |
215 | |
216 | if (!$this->running_javascript()) { | |
217 | throw new DriverException('Waits are disabled in scenarios without Javascript support'); | |
218 | } | |
219 | ||
d1e55a47 DM |
220 | $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); |
221 | } | |
222 | ||
223 | /** | |
224 | * Waits until the provided element selector exists in the DOM | |
225 | * | |
226 | * Using the protected method as this method will be usually | |
227 | * called by other methods which are not returning a set of | |
228 | * steps and performs the actions directly, so it would not | |
229 | * be executed if it returns another step. | |
230 | ||
231 | * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" exists$/ | |
232 | * @param string $element | |
233 | * @param string $selector | |
234 | * @return void | |
235 | */ | |
236 | public function wait_until_exists($element, $selectortype) { | |
237 | $this->ensure_element_exists($element, $selectortype); | |
238 | } | |
239 | ||
240 | /** | |
241 | * Waits until the provided element does not exist in the DOM | |
242 | * | |
243 | * Using the protected method as this method will be usually | |
244 | * called by other methods which are not returning a set of | |
245 | * steps and performs the actions directly, so it would not | |
246 | * be executed if it returns another step. | |
247 | ||
248 | * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" does not exist$/ | |
249 | * @param string $element | |
250 | * @param string $selector | |
251 | * @return void | |
252 | */ | |
253 | public function wait_until_does_not_exists($element, $selectortype) { | |
254 | $this->ensure_element_does_not_exist($element, $selectortype); | |
786ea937 DM |
255 | } |
256 | ||
257 | /** | |
40923977 | 258 | * Generic mouse over action. Mouse over a element of the specified type. |
786ea937 | 259 | * |
40923977 DM |
260 | * @When /^I hover "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ |
261 | * @param string $element Element we look for | |
262 | * @param string $selectortype The type of what we look for | |
786ea937 | 263 | */ |
40923977 | 264 | public function i_hover($element, $selectortype) { |
1f9ffbdb | 265 | |
40923977 DM |
266 | // Gets the node based on the requested selector type and locator. |
267 | $node = $this->get_selected_node($selectortype, $element); | |
786ea937 DM |
268 | $node->mouseOver(); |
269 | } | |
270 | ||
40923977 DM |
271 | /** |
272 | * Generic click action. Click on the element of the specified type. | |
273 | * | |
274 | * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ | |
275 | * @param string $element Element we look for | |
276 | * @param string $selectortype The type of what we look for | |
277 | */ | |
278 | public function i_click_on($element, $selectortype) { | |
279 | ||
280 | // Gets the node based on the requested selector type and locator. | |
281 | $node = $this->get_selected_node($selectortype, $element); | |
d1e55a47 | 282 | $this->ensure_node_is_visible($node); |
40923977 DM |
283 | $node->click(); |
284 | } | |
285 | ||
d7a0b721 RT |
286 | /** |
287 | * Sets the focus and takes away the focus from an element, generating blur JS event. | |
288 | * | |
289 | * @When /^I take focus off "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ | |
290 | * @param string $element Element we look for | |
291 | * @param string $selectortype The type of what we look for | |
292 | */ | |
293 | public function i_take_focus_off_field($element, $selectortype) { | |
294 | if (!$this->running_javascript()) { | |
295 | throw new ExpectationException('Can\'t take focus off from "' . $element . '" in non-js mode', $this->getSession()); | |
296 | } | |
297 | // Gets the node based on the requested selector type and locator. | |
298 | $node = $this->get_selected_node($selectortype, $element); | |
299 | $this->ensure_node_is_visible($node); | |
300 | ||
301 | // Ensure element is focused before taking it off. | |
302 | $node->focus(); | |
303 | $node->blur(); | |
304 | } | |
305 | ||
b28b374f DM |
306 | /** |
307 | * Clicks the specified element and confirms the expected dialogue. | |
308 | * | |
309 | * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" confirming the dialogue$/ | |
310 | * @throws ElementNotFoundException Thrown by behat_base::find | |
311 | * @param string $link | |
312 | */ | |
313 | public function i_click_on_confirming_the_dialogue($element, $selectortype) { | |
314 | $this->i_click_on($element, $selectortype); | |
315 | $this->accept_currently_displayed_alert_dialog(); | |
316 | } | |
317 | ||
072f67fc DM |
318 | /** |
319 | * Click on the element of the specified type which is located inside the second element. | |
320 | * | |
321 | * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ | |
322 | * @param string $element Element we look for | |
323 | * @param string $selectortype The type of what we look for | |
324 | * @param string $nodeelement Element we look in | |
325 | * @param string $nodeselectortype The type of selector where we look in | |
326 | */ | |
327 | public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) { | |
328 | ||
329 | $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); | |
d1e55a47 | 330 | $this->ensure_node_is_visible($node); |
072f67fc DM |
331 | $node->click(); |
332 | } | |
333 | ||
563514b1 | 334 | /** |
7daab401 | 335 | * Drags and drops the specified element to the specified container. This step does not work in all the browsers, consider it experimental. |
563514b1 DM |
336 | * |
337 | * The steps definitions calling this step as part of them should | |
338 | * manage the wait times by themselves as the times and when the | |
339 | * waits should be done depends on what is being dragged & dropper. | |
340 | * | |
341 | * @Given /^I drag "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" and I drop it in "(?P<container_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ | |
342 | * @param string $element | |
343 | * @param string $selectortype | |
344 | * @param string $containerelement | |
345 | * @param string $containerselectortype | |
346 | */ | |
347 | public function i_drag_and_i_drop_it_in($element, $selectortype, $containerelement, $containerselectortype) { | |
348 | ||
349 | list($sourceselector, $sourcelocator) = $this->transform_selector($selectortype, $element); | |
350 | $sourcexpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($sourceselector, $sourcelocator); | |
351 | ||
352 | list($containerselector, $containerlocator) = $this->transform_selector($containerselectortype, $containerelement); | |
353 | $destinationxpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($containerselector, $containerlocator); | |
354 | ||
355 | $this->getSession()->getDriver()->dragTo($sourcexpath, $destinationxpath); | |
356 | } | |
357 | ||
63950e4d DM |
358 | /** |
359 | * Checks, that the specified element is visible. Only available in tests using Javascript. | |
360 | * | |
361 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should be visible$/ | |
362 | * @throws ElementNotFoundException | |
363 | * @throws ExpectationException | |
364 | * @throws DriverException | |
365 | * @param string $element | |
366 | * @param string $selectortype | |
367 | * @return void | |
368 | */ | |
369 | public function should_be_visible($element, $selectortype) { | |
370 | ||
371 | if (!$this->running_javascript()) { | |
372 | throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); | |
373 | } | |
374 | ||
375 | $node = $this->get_selected_node($selectortype, $element); | |
376 | if (!$node->isVisible()) { | |
377 | throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is not visible', $this->getSession()); | |
378 | } | |
379 | } | |
380 | ||
381 | /** | |
74c78e74 | 382 | * Checks, that the existing element is not visible. Only available in tests using Javascript. |
63950e4d | 383 | * |
74c78e74 DM |
384 | * As a "not" method, it's performance could not be good, but in this |
385 | * case the performance is good because the element must exist, | |
386 | * otherwise there would be a ElementNotFoundException, also here we are | |
387 | * not spinning until the element is visible. | |
c1faf86b | 388 | * |
63950e4d DM |
389 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/ |
390 | * @throws ElementNotFoundException | |
391 | * @throws ExpectationException | |
392 | * @param string $element | |
393 | * @param string $selectortype | |
394 | * @return void | |
395 | */ | |
396 | public function should_not_be_visible($element, $selectortype) { | |
397 | ||
398 | try { | |
399 | $this->should_be_visible($element, $selectortype); | |
400 | throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is visible', $this->getSession()); | |
401 | } catch (ExpectationException $e) { | |
402 | // All as expected. | |
403 | } | |
404 | } | |
405 | ||
406 | /** | |
407 | * Checks, that the specified element is visible inside the specified container. Only available in tests using Javascript. | |
408 | * | |
409 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should be visible$/ | |
410 | * @throws ElementNotFoundException | |
411 | * @throws DriverException | |
412 | * @throws ExpectationException | |
413 | * @param string $element Element we look for | |
414 | * @param string $selectortype The type of what we look for | |
415 | * @param string $nodeelement Element we look in | |
416 | * @param string $nodeselectortype The type of selector where we look in | |
417 | */ | |
418 | public function in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { | |
419 | ||
420 | if (!$this->running_javascript()) { | |
421 | throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); | |
422 | } | |
423 | ||
424 | $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); | |
425 | if (!$node->isVisible()) { | |
426 | throw new ExpectationException( | |
427 | '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is not visible', | |
428 | $this->getSession() | |
429 | ); | |
430 | } | |
431 | } | |
432 | ||
433 | /** | |
74c78e74 DM |
434 | * Checks, that the existing element is not visible inside the existing container. Only available in tests using Javascript. |
435 | * | |
436 | * As a "not" method, it's performance could not be good, but in this | |
437 | * case the performance is good because the element must exist, | |
438 | * otherwise there would be a ElementNotFoundException, also here we are | |
439 | * not spinning until the element is visible. | |
63950e4d DM |
440 | * |
441 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should not be visible$/ | |
442 | * @throws ElementNotFoundException | |
443 | * @throws ExpectationException | |
444 | * @param string $element Element we look for | |
445 | * @param string $selectortype The type of what we look for | |
446 | * @param string $nodeelement Element we look in | |
447 | * @param string $nodeselectortype The type of selector where we look in | |
448 | */ | |
449 | public function in_the_should_not_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { | |
450 | ||
451 | try { | |
452 | $this->in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype); | |
453 | throw new ExpectationException( | |
454 | '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is visible', | |
455 | $this->getSession() | |
456 | ); | |
457 | } catch (ExpectationException $e) { | |
458 | // All as expected. | |
459 | } | |
460 | } | |
461 | ||
786ea937 | 462 | /** |
e9af3ed3 | 463 | * Checks, that page contains specified text. It also checks if the text is visible when running Javascript tests. |
786ea937 | 464 | * |
786ea937 | 465 | * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)"$/ |
9a1f4922 | 466 | * @throws ExpectationException |
40923977 | 467 | * @param string $text |
786ea937 DM |
468 | */ |
469 | public function assert_page_contains_text($text) { | |
9a1f4922 | 470 | |
e9af3ed3 DM |
471 | // Looking for all the matching nodes without any other descendant matching the |
472 | // same xpath (we are using contains(., ....). | |
9a1f4922 | 473 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); |
e9af3ed3 DM |
474 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . |
475 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
9a1f4922 | 476 | |
9a1f4922 | 477 | try { |
e9af3ed3 | 478 | $nodes = $this->find_all('xpath', $xpath); |
c1faf86b DM |
479 | } catch (ElementNotFoundException $e) { |
480 | throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession()); | |
481 | } | |
e9af3ed3 | 482 | |
c1faf86b DM |
483 | // If we are not running javascript we have enough with the |
484 | // element existing as we can't check if it is visible. | |
485 | if (!$this->running_javascript()) { | |
486 | return; | |
487 | } | |
488 | ||
489 | // We spin as we don't have enough checking that the element is there, we | |
74c78e74 DM |
490 | // should also ensure that the element is visible. Using microsleep as this |
491 | // is a repeated step and global performance is important. | |
c1faf86b DM |
492 | $this->spin( |
493 | function($context, $args) { | |
494 | ||
495 | foreach ($args['nodes'] as $node) { | |
e9af3ed3 | 496 | if ($node->isVisible()) { |
c1faf86b | 497 | return true; |
e9af3ed3 DM |
498 | } |
499 | } | |
500 | ||
c1faf86b DM |
501 | // If non of the nodes is visible we loop again. |
502 | throw new ExpectationException('"' . $args['text'] . '" text was found but was not visible', $context->getSession()); | |
503 | }, | |
74c78e74 DM |
504 | array('nodes' => $nodes, 'text' => $text), |
505 | false, | |
506 | false, | |
507 | true | |
c1faf86b | 508 | ); |
e9af3ed3 | 509 | |
786ea937 DM |
510 | } |
511 | ||
512 | /** | |
e9af3ed3 | 513 | * Checks, that page doesn't contain specified text. When running Javascript tests it also considers that texts may be hidden. |
786ea937 | 514 | * |
786ea937 | 515 | * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)"$/ |
9a1f4922 | 516 | * @throws ExpectationException |
40923977 | 517 | * @param string $text |
786ea937 DM |
518 | */ |
519 | public function assert_page_not_contains_text($text) { | |
9a1f4922 | 520 | |
c1faf86b DM |
521 | // Looking for all the matching nodes without any other descendant matching the |
522 | // same xpath (we are using contains(., ....). | |
523 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); | |
524 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . | |
525 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
526 | ||
527 | // We should wait a while to ensure that the page is not still loading elements. | |
74c78e74 DM |
528 | // Waiting less than self::TIMEOUT as we already waited for the DOM to be ready and |
529 | // all JS to be executed. | |
9a1f4922 | 530 | try { |
74c78e74 | 531 | $nodes = $this->find_all('xpath', $xpath, false, false, self::REDUCED_TIMEOUT); |
c1faf86b DM |
532 | } catch (ElementNotFoundException $e) { |
533 | // All ok. | |
5458ab3e | 534 | return; |
9a1f4922 | 535 | } |
5458ab3e | 536 | |
c1faf86b DM |
537 | // If we are not running javascript we have enough with the |
538 | // element existing as we can't check if it is hidden. | |
539 | if (!$this->running_javascript()) { | |
540 | throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession()); | |
541 | } | |
542 | ||
543 | // If the element is there we should be sure that it is not visible. | |
544 | $this->spin( | |
545 | function($context, $args) { | |
546 | ||
547 | foreach ($args['nodes'] as $node) { | |
548 | if ($node->isVisible()) { | |
549 | throw new ExpectationException('"' . $args['text'] . '" text was found in the page', $context->getSession()); | |
550 | } | |
551 | } | |
552 | ||
553 | // If non of the found nodes is visible we consider that the text is not visible. | |
554 | return true; | |
555 | }, | |
74c78e74 DM |
556 | array('nodes' => $nodes, 'text' => $text), |
557 | self::REDUCED_TIMEOUT, | |
558 | false, | |
559 | true | |
c1faf86b DM |
560 | ); |
561 | ||
786ea937 DM |
562 | } |
563 | ||
564 | /** | |
e9af3ed3 | 565 | * Checks, that the specified element contains the specified text. When running Javascript tests it also considers that texts may be hidden. |
786ea937 | 566 | * |
40923977 | 567 | * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ |
5458ab3e DM |
568 | * @throws ElementNotFoundException |
569 | * @throws ExpectationException | |
40923977 DM |
570 | * @param string $text |
571 | * @param string $element Element we look in. | |
572 | * @param string $selectortype The type of element where we are looking in. | |
786ea937 | 573 | */ |
40923977 DM |
574 | public function assert_element_contains_text($text, $element, $selectortype) { |
575 | ||
5458ab3e DM |
576 | // Getting the container where the text should be found. |
577 | $container = $this->get_selected_node($selectortype, $element); | |
578 | ||
e9af3ed3 DM |
579 | // Looking for all the matching nodes without any other descendant matching the |
580 | // same xpath (we are using contains(., ....). | |
5458ab3e | 581 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); |
e9af3ed3 DM |
582 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . |
583 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
5458ab3e DM |
584 | |
585 | // Wait until it finds the text inside the container, otherwise custom exception. | |
586 | try { | |
e9af3ed3 | 587 | $nodes = $this->find_all('xpath', $xpath, false, $container); |
c1faf86b DM |
588 | } catch (ElementNotFoundException $e) { |
589 | throw new ExpectationException('"' . $text . '" text was not found in the "' . $element . '" element', $this->getSession()); | |
590 | } | |
e9af3ed3 | 591 | |
c1faf86b DM |
592 | // If we are not running javascript we have enough with the |
593 | // element existing as we can't check if it is visible. | |
594 | if (!$this->running_javascript()) { | |
595 | return; | |
596 | } | |
597 | ||
74c78e74 DM |
598 | // We also check the element visibility when running JS tests. Using microsleep as this |
599 | // is a repeated step and global performance is important. | |
c1faf86b DM |
600 | $this->spin( |
601 | function($context, $args) { | |
602 | ||
603 | foreach ($args['nodes'] as $node) { | |
e9af3ed3 | 604 | if ($node->isVisible()) { |
c1faf86b | 605 | return true; |
e9af3ed3 DM |
606 | } |
607 | } | |
608 | ||
c1faf86b DM |
609 | throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element but was not visible', $context->getSession()); |
610 | }, | |
74c78e74 DM |
611 | array('nodes' => $nodes, 'text' => $text, 'element' => $element), |
612 | false, | |
613 | false, | |
614 | true | |
c1faf86b | 615 | ); |
786ea937 DM |
616 | } |
617 | ||
618 | /** | |
e9af3ed3 | 619 | * Checks, that the specified element does not contain the specified text. When running Javascript tests it also considers that texts may be hidden. |
786ea937 | 620 | * |
40923977 | 621 | * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ |
5458ab3e DM |
622 | * @throws ElementNotFoundException |
623 | * @throws ExpectationException | |
40923977 DM |
624 | * @param string $text |
625 | * @param string $element Element we look in. | |
626 | * @param string $selectortype The type of element where we are looking in. | |
786ea937 | 627 | */ |
40923977 DM |
628 | public function assert_element_not_contains_text($text, $element, $selectortype) { |
629 | ||
c1faf86b DM |
630 | // Getting the container where the text should be found. |
631 | $container = $this->get_selected_node($selectortype, $element); | |
632 | ||
633 | // Looking for all the matching nodes without any other descendant matching the | |
634 | // same xpath (we are using contains(., ....). | |
635 | $xpathliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($text); | |
636 | $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . | |
637 | "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; | |
638 | ||
639 | // We should wait a while to ensure that the page is not still loading elements. | |
640 | // Giving preference to the reliability of the results rather than to the performance. | |
5458ab3e | 641 | try { |
74c78e74 | 642 | $nodes = $this->find_all('xpath', $xpath, false, $container, self::REDUCED_TIMEOUT); |
c1faf86b DM |
643 | } catch (ElementNotFoundException $e) { |
644 | // All ok. | |
5458ab3e DM |
645 | return; |
646 | } | |
647 | ||
c1faf86b DM |
648 | // If we are not running javascript we have enough with the |
649 | // element not being found as we can't check if it is visible. | |
650 | if (!$this->running_javascript()) { | |
651 | throw new ExpectationException('"' . $text . '" text was found in the "' . $element . '" element', $this->getSession()); | |
652 | } | |
653 | ||
654 | // We need to ensure all the found nodes are hidden. | |
655 | $this->spin( | |
656 | function($context, $args) { | |
657 | ||
658 | foreach ($args['nodes'] as $node) { | |
659 | if ($node->isVisible()) { | |
660 | throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element', $context->getSession()); | |
661 | } | |
662 | } | |
663 | ||
664 | // If all the found nodes are hidden we are happy. | |
665 | return true; | |
666 | }, | |
74c78e74 DM |
667 | array('nodes' => $nodes, 'text' => $text, 'element' => $element), |
668 | self::REDUCED_TIMEOUT, | |
669 | false, | |
670 | true | |
c1faf86b | 671 | ); |
786ea937 DM |
672 | } |
673 | ||
60054942 DM |
674 | /** |
675 | * Checks, that the first specified element appears before the second one. | |
676 | * | |
677 | * @Given /^"(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear before "(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ | |
678 | * @throws ExpectationException | |
679 | * @param string $preelement The locator of the preceding element | |
680 | * @param string $preselectortype The locator of the preceding element | |
681 | * @param string $postelement The locator of the latest element | |
682 | * @param string $postselectortype The selector type of the latest element | |
683 | */ | |
684 | public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) { | |
685 | ||
686 | // We allow postselectortype as a non-text based selector. | |
687 | list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); | |
688 | list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); | |
689 | ||
690 | $prexpath = $this->find($preselector, $prelocator)->getXpath(); | |
691 | $postxpath = $this->find($postselector, $postlocator)->getXpath(); | |
692 | ||
693 | // Using following xpath axe to find it. | |
694 | $msg = '"'.$preelement.'" "'.$preselectortype.'" does not appear before "'.$postelement.'" "'.$postselectortype.'"'; | |
695 | $xpath = $prexpath.'/following::*[contains(., '.$postxpath.')]'; | |
696 | if (!$this->getSession()->getDriver()->find($xpath)) { | |
697 | throw new ExpectationException($msg, $this->getSession()); | |
698 | } | |
699 | } | |
700 | ||
701 | /** | |
702 | * Checks, that the first specified element appears after the second one. | |
703 | * | |
704 | * @Given /^"(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear after "(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ | |
705 | * @throws ExpectationException | |
706 | * @param string $postelement The locator of the latest element | |
707 | * @param string $postselectortype The selector type of the latest element | |
708 | * @param string $preelement The locator of the preceding element | |
709 | * @param string $preselectortype The locator of the preceding element | |
710 | */ | |
711 | public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) { | |
712 | ||
713 | // We allow postselectortype as a non-text based selector. | |
714 | list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); | |
715 | list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); | |
716 | ||
717 | $postxpath = $this->find($postselector, $postlocator)->getXpath(); | |
718 | $prexpath = $this->find($preselector, $prelocator)->getXpath(); | |
719 | ||
720 | // Using preceding xpath axe to find it. | |
721 | $msg = '"'.$postelement.'" "'.$postselectortype.'" does not appear after "'.$preelement.'" "'.$preselectortype.'"'; | |
722 | $xpath = $postxpath.'/preceding::*[contains(., '.$prexpath.')]'; | |
723 | if (!$this->getSession()->getDriver()->find($xpath)) { | |
724 | throw new ExpectationException($msg, $this->getSession()); | |
725 | } | |
726 | } | |
727 | ||
786ea937 | 728 | /** |
40923977 | 729 | * Checks, that element of specified type is disabled. |
786ea937 | 730 | * |
40923977 | 731 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be disabled$/ |
1f9ffbdb | 732 | * @throws ExpectationException Thrown by behat_base::find |
40923977 DM |
733 | * @param string $element Element we look in |
734 | * @param string $selectortype The type of element where we are looking in. | |
786ea937 | 735 | */ |
40923977 | 736 | public function the_element_should_be_disabled($element, $selectortype) { |
786ea937 | 737 | |
40923977 DM |
738 | // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. |
739 | $node = $this->get_selected_node($selectortype, $element); | |
786ea937 DM |
740 | |
741 | if (!$node->hasAttribute('disabled')) { | |
742 | throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession()); | |
743 | } | |
744 | } | |
745 | ||
746 | /** | |
40923977 | 747 | * Checks, that element of specified type is enabled. |
786ea937 | 748 | * |
40923977 | 749 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be enabled$/ |
1f9ffbdb | 750 | * @throws ExpectationException Thrown by behat_base::find |
40923977 DM |
751 | * @param string $element Element we look on |
752 | * @param string $selectortype The type of where we look | |
786ea937 | 753 | */ |
40923977 | 754 | public function the_element_should_be_enabled($element, $selectortype) { |
1f9ffbdb | 755 | |
40923977 DM |
756 | // Transforming from steps definitions selector/locator format to mink format and getting the NodeElement. |
757 | $node = $this->get_selected_node($selectortype, $element); | |
786ea937 DM |
758 | |
759 | if ($node->hasAttribute('disabled')) { | |
760 | throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession()); | |
761 | } | |
762 | } | |
763 | ||
a2d3e3b6 MN |
764 | /** |
765 | * Checks the provided element and selector type are readonly on the current page. | |
766 | * | |
767 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be readonly$/ | |
768 | * @throws ExpectationException Thrown by behat_base::find | |
769 | * @param string $element Element we look in | |
770 | * @param string $selectortype The type of element where we are looking in. | |
771 | */ | |
772 | public function the_element_should_be_readonly($element, $selectortype) { | |
773 | // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. | |
774 | $node = $this->get_selected_node($selectortype, $element); | |
775 | ||
776 | if (!$node->hasAttribute('readonly')) { | |
777 | throw new ExpectationException('The element "' . $element . '" is not readonly', $this->getSession()); | |
778 | } | |
779 | } | |
780 | ||
781 | /** | |
782 | * Checks the provided element and selector type are not readonly on the current page. | |
783 | * | |
784 | * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not be readonly$/ | |
785 | * @throws ExpectationException Thrown by behat_base::find | |
786 | * @param string $element Element we look in | |
787 | * @param string $selectortype The type of element where we are looking in. | |
788 | */ | |
789 | public function the_element_should_not_be_readonly($element, $selectortype) { | |
790 | // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. | |
791 | $node = $this->get_selected_node($selectortype, $element); | |
792 | ||
793 | if ($node->hasAttribute('readonly')) { | |
794 | throw new ExpectationException('The element "' . $element . '" is readonly', $this->getSession()); | |
795 | } | |
796 | } | |
797 | ||
ca4f33a7 | 798 | /** |
62eb5c46 EL |
799 | * Checks the provided element and selector type exists in the current page. |
800 | * | |
801 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
ca4f33a7 | 802 | * |
c51c3b55 | 803 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist$/ |
ca4f33a7 DM |
804 | * @throws ElementNotFoundException Thrown by behat_base::find |
805 | * @param string $element The locator of the specified selector | |
806 | * @param string $selectortype The selector type | |
807 | */ | |
c51c3b55 | 808 | public function should_exist($element, $selectortype) { |
ca4f33a7 DM |
809 | |
810 | // Getting Mink selector and locator. | |
811 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
812 | ||
813 | // Will throw an ElementNotFoundException if it does not exist. | |
814 | $this->find($selector, $locator); | |
815 | } | |
816 | ||
817 | /** | |
62eb5c46 EL |
818 | * Checks that the provided element and selector type not exists in the current page. |
819 | * | |
820 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
ca4f33a7 | 821 | * |
c51c3b55 | 822 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist$/ |
ca4f33a7 DM |
823 | * @throws ExpectationException |
824 | * @param string $element The locator of the specified selector | |
825 | * @param string $selectortype The selector type | |
826 | */ | |
c51c3b55 | 827 | public function should_not_exist($element, $selectortype) { |
ca4f33a7 | 828 | |
74c78e74 DM |
829 | // Getting Mink selector and locator. |
830 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
831 | ||
ca4f33a7 | 832 | try { |
74c78e74 DM |
833 | |
834 | // Using directly the spin method as we want a reduced timeout but there is no | |
835 | // need for a 0.1 seconds interval because in the optimistic case we will timeout. | |
836 | $params = array('selector' => $selector, 'locator' => $locator); | |
837 | // The exception does not really matter as we will catch it and will never "explode". | |
838 | $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $element); | |
839 | ||
840 | // If all goes good it will throw an ElementNotFoundExceptionn that we will catch. | |
841 | $this->spin( | |
842 | function($context, $args) { | |
843 | return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']); | |
844 | }, | |
845 | $params, | |
20503d15 | 846 | self::REDUCED_TIMEOUT, |
74c78e74 | 847 | $exception, |
20503d15 | 848 | false |
74c78e74 DM |
849 | ); |
850 | ||
ca4f33a7 | 851 | throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the current page', $this->getSession()); |
62eb5c46 | 852 | } catch (ElementNotFoundException $e) { |
ca4f33a7 DM |
853 | // It passes. |
854 | return; | |
855 | } | |
856 | } | |
857 | ||
066ef320 JM |
858 | /** |
859 | * This step triggers cron like a user would do going to admin/cron.php. | |
860 | * | |
861 | * @Given /^I trigger cron$/ | |
862 | */ | |
863 | public function i_trigger_cron() { | |
864 | $this->getSession()->visit($this->locate_path('/admin/cron.php')); | |
865 | } | |
866 | ||
a2d3e3b6 MN |
867 | /** |
868 | * Checks that an element and selector type exists in another element and selector type on the current page. | |
869 | * | |
870 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
871 | * | |
872 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ | |
873 | * @throws ElementNotFoundException Thrown by behat_base::find | |
874 | * @param string $element The locator of the specified selector | |
875 | * @param string $selectortype The selector type | |
876 | * @param string $containerelement The container selector type | |
877 | * @param string $containerselectortype The container locator | |
878 | */ | |
879 | public function should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { | |
880 | // Get the container node. | |
881 | $containernode = $this->get_selected_node($containerselectortype, $containerelement); | |
882 | ||
883 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
884 | ||
885 | // Specific exception giving info about where can't we find the element. | |
886 | $locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"'; | |
887 | $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg); | |
888 | ||
889 | // Looks for the requested node inside the container node. | |
890 | $this->find($selector, $locator, $exception, $containernode); | |
891 | } | |
892 | ||
893 | /** | |
894 | * Checks that an element and selector type does not exist in another element and selector type on the current page. | |
895 | * | |
896 | * This step is for advanced users, use it if you don't find anything else suitable for what you need. | |
897 | * | |
898 | * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ | |
899 | * @throws ExpectationException | |
900 | * @param string $element The locator of the specified selector | |
901 | * @param string $selectortype The selector type | |
902 | * @param string $containerelement The container selector type | |
903 | * @param string $containerselectortype The container locator | |
904 | */ | |
905 | public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { | |
74c78e74 DM |
906 | |
907 | // Get the container node; here we throw an exception | |
908 | // if the container node does not exist. | |
909 | $containernode = $this->get_selected_node($containerselectortype, $containerelement); | |
910 | ||
911 | list($selector, $locator) = $this->transform_selector($selectortype, $element); | |
912 | ||
913 | // Will throw an ElementNotFoundException if it does not exist, but, actually | |
759b323e | 914 | // it should not exist, so we try & catch it. |
a2d3e3b6 | 915 | try { |
74c78e74 DM |
916 | // Would be better to use a 1 second sleep because the element should not be there, |
917 | // but we would need to duplicate the whole find_all() logic to do it, the benefit of | |
918 | // changing to 1 second sleep is not significant. | |
919 | $this->find($selector, $locator, false, $containernode, self::REDUCED_TIMEOUT); | |
a2d3e3b6 MN |
920 | throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the "' . |
921 | $containerelement . '" "' . $containerselectortype . '"', $this->getSession()); | |
922 | } catch (ElementNotFoundException $e) { | |
923 | // It passes. | |
924 | return; | |
925 | } | |
926 | } | |
3b0b5e57 RT |
927 | |
928 | /** | |
929 | * Change browser window size small: 640x480, medium: 1024x768, large: 2560x1600, custom: widthxheight | |
930 | * | |
931 | * Example: I change window size to "small" or I change window size to "1024x768" | |
932 | * | |
933 | * @throws ExpectationException | |
934 | * @Then /^I change window size to "([^"](small|medium|large|\d+x\d+))"$/ | |
935 | * @param string $windowsize size of the window (small|medium|large|wxh). | |
936 | */ | |
937 | public function i_change_window_size_to($windowsize) { | |
938 | $this->resize_window($windowsize); | |
939 | } | |
c0fb7f44 | 940 | |
941 | /** | |
942 | * Checks whether there is an attribute on the given element that contains the specified text. | |
943 | * | |
944 | * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should contain "(?P<text_string>(?:[^"]|\\")*)"$/ | |
945 | * @throws ExpectationException | |
946 | * @param string $attribute Name of attribute | |
947 | * @param string $element The locator of the specified selector | |
948 | * @param string $selectortype The selector type | |
949 | * @param string $text Expected substring | |
950 | */ | |
951 | public function the_attribute_of_should_contain($attribute, $element, $selectortype, $text) { | |
952 | // Get the container node (exception if it doesn't exist). | |
953 | $containernode = $this->get_selected_node($selectortype, $element); | |
954 | $value = $containernode->getAttribute($attribute); | |
955 | if ($value == null) { | |
956 | throw new ExpectationException('The attribute "' . $attribute. '" does not exist', | |
957 | $this->getSession()); | |
958 | } else if (strpos($value, $text) === false) { | |
959 | throw new ExpectationException('The attribute "' . $attribute . | |
960 | '" does not contain "' . $text . '" (actual value: "' . $value . '")', | |
961 | $this->getSession()); | |
962 | } | |
963 | } | |
90c3ffe3 | 964 | |
965 | /** | |
966 | * Checks that the attribute on the given element does not contain the specified text. | |
967 | * | |
968 | * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not contain "(?P<text_string>(?:[^"]|\\")*)"$/ | |
969 | * @throws ExpectationException | |
970 | * @param string $attribute Name of attribute | |
971 | * @param string $element The locator of the specified selector | |
972 | * @param string $selectortype The selector type | |
973 | * @param string $text Expected substring | |
974 | */ | |
975 | public function the_attribute_of_should_not_contain($attribute, $element, $selectortype, $text) { | |
976 | // Get the container node (exception if it doesn't exist). | |
977 | $containernode = $this->get_selected_node($selectortype, $element); | |
978 | $value = $containernode->getAttribute($attribute); | |
979 | if ($value == null) { | |
980 | throw new ExpectationException('The attribute "' . $attribute. '" does not exist', | |
981 | $this->getSession()); | |
982 | } else if (strpos($value, $text) !== false) { | |
983 | throw new ExpectationException('The attribute "' . $attribute . | |
984 | '" contains "' . $text . '" (value: "' . $value . '")', | |
985 | $this->getSession()); | |
986 | } | |
987 | } | |
641459a8 RT |
988 | |
989 | /** | |
990 | * Checks the provided value exists in specific row/column of table. | |
991 | * | |
992 | * @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should contain "(?P<value_string>[^"]*)"$/ | |
993 | * @throws ElementNotFoundException | |
994 | * @param string $row row text which will be looked in. | |
97329f1b | 995 | * @param string $column column text to search (or numeric value for the column position) |
641459a8 RT |
996 | * @param string $table table id/class/caption |
997 | * @param string $value text to check. | |
998 | */ | |
999 | public function row_column_of_table_should_contain($row, $column, $table, $value) { | |
1000 | $tablenode = $this->get_selected_node('table', $table); | |
1001 | $tablexpath = $tablenode->getXpath(); | |
1002 | ||
bd855fd5 MG |
1003 | $rowliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($row); |
1004 | $valueliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($value); | |
1005 | $columnliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($column); | |
1006 | ||
97329f1b MG |
1007 | if (preg_match('/^-?(\d+)-?$/', $column, $columnasnumber)) { |
1008 | // Column indicated as a number, just use it as position of the column. | |
1009 | $columnpositionxpath = "/child::*[position() = {$columnasnumber[1]}]"; | |
1010 | } else { | |
1011 | // Header can be in thead or tbody (first row), following xpath should work. | |
1012 | $theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" . | |
1013 | $columnliteral . "])]"; | |
1014 | $tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" . | |
1015 | $columnliteral . "])]"; | |
1016 | ||
1017 | // Check if column exists. | |
1018 | $columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]"; | |
1019 | $columnheader = $this->getSession()->getDriver()->find($columnheaderxpath); | |
1020 | if (empty($columnheader)) { | |
1021 | $columnexceptionmsg = $column . '" in table "' . $table . '"'; | |
1022 | throw new ElementNotFoundException($this->getSession(), "\n$columnheaderxpath\n\n".'Column', null, $columnexceptionmsg); | |
1023 | } | |
1024 | // Following conditions were considered before finding column count. | |
1025 | // 1. Table header can be in thead/tr/th or tbody/tr/td[1]. | |
1026 | // 2. First column can have th (Gradebook -> user report), so having lenient sibling check. | |
1027 | $columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath . | |
1028 | "/preceding-sibling::*) + 1]"; | |
641459a8 RT |
1029 | } |
1030 | ||
1031 | // Check if value exists in specific row/column. | |
1032 | // Get row xpath. | |
bd855fd5 | 1033 | $rowxpath = $tablexpath."/tbody/tr[th[normalize-space(.)=" . $rowliteral . "] | td[normalize-space(.)=" . $rowliteral . "]]"; |
641459a8 | 1034 | |
bd855fd5 | 1035 | $columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]"; |
97329f1b | 1036 | |
641459a8 RT |
1037 | // Looks for the requested node inside the container node. |
1038 | $coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath); | |
1039 | if (empty($coumnnode)) { | |
97329f1b MG |
1040 | $locatorexceptionmsg = $value . '" in "' . $row . '" row with column "' . $column; |
1041 | throw new ElementNotFoundException($this->getSession(), "\n$columnvaluexpath\n\n".'Column value', null, $locatorexceptionmsg); | |
641459a8 RT |
1042 | } |
1043 | } | |
1044 | ||
1045 | /** | |
1046 | * Checks the provided value should not exist in specific row/column of table. | |
1047 | * | |
1048 | * @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should not contain "(?P<value_string>[^"]*)"$/ | |
1049 | * @throws ElementNotFoundException | |
1050 | * @param string $row row text which will be looked in. | |
1051 | * @param string $column column text to search | |
1052 | * @param string $table table id/class/caption | |
1053 | * @param string $value text to check. | |
1054 | */ | |
1055 | public function row_column_of_table_should_not_contain($row, $column, $table, $value) { | |
1056 | try { | |
1057 | $this->row_column_of_table_should_contain($row, $column, $table, $value); | |
1058 | // Throw exception if found. | |
1059 | throw new ExpectationException( | |
1060 | '"' . $column . '" with value "' . $value . '" is present in "' . $row . '" row for table "' . $table . '"', | |
1061 | $this->getSession() | |
1062 | ); | |
1063 | } catch (ElementNotFoundException $e) { | |
1064 | // Table row/column doesn't contain this value. Nothing to do. | |
1065 | return; | |
1066 | } | |
1067 | } | |
1068 | ||
1069 | /** | |
1070 | * Checks that the provided value exist in table. | |
1071 | * More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps. | |
1072 | * | |
97329f1b MG |
1073 | * First row may contain column headers or numeric indexes of the columns |
1074 | * (syntax -1- is also considered to be column index). Column indexes are | |
1075 | * useful in case of multirow headers and/or presence of cells with colspan. | |
1076 | * | |
641459a8 RT |
1077 | * @Then /^the following should exist in the "(?P<table_string>[^"]*)" table:$/ |
1078 | * @throws ExpectationException | |
1079 | * @param string $table name of table | |
1080 | * @param TableNode $data table with first row as header and following values | |
1081 | * | Header 1 | Header 2 | Header 3 | | |
1082 | * | Value 1 | Value 2 | Value 3| | |
1083 | */ | |
08678f6f | 1084 | public function following_should_exist_in_the_table($table, TableNode $data) { |
641459a8 RT |
1085 | $datahash = $data->getHash(); |
1086 | ||
97329f1b MG |
1087 | foreach ($datahash as $row) { |
1088 | $firstcell = null; | |
1089 | foreach ($row as $column => $value) { | |
1090 | if ($firstcell === null) { | |
1091 | $firstcell = $value; | |
1092 | } else { | |
1093 | $this->row_column_of_table_should_contain($firstcell, $column, $table, $value); | |
1094 | } | |
641459a8 RT |
1095 | } |
1096 | } | |
1097 | } | |
1098 | ||
1099 | /** | |
1100 | * Checks that the provided value exist in table. | |
1101 | * More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps. | |
1102 | * | |
1103 | * @Then /^the following should not exist in the "(?P<table_string>[^"]*)" table:$/ | |
1104 | * @throws ExpectationException | |
1105 | * @param string $table name of table | |
1106 | * @param TableNode $data table with first row as header and following values | |
1107 | * | Header 1 | Header 2 | Header 3 | | |
1108 | * | Value 1 | Value 2 | Value 3| | |
1109 | */ | |
08678f6f | 1110 | public function following_should_not_exist_in_the_table($table, TableNode $data) { |
641459a8 RT |
1111 | $datahash = $data->getHash(); |
1112 | ||
1113 | foreach ($datahash as $value) { | |
1114 | $row = array_shift($value); | |
1115 | foreach ($value as $column => $value) { | |
1116 | try { | |
1117 | $this->row_column_of_table_should_contain($row, $column, $table, $value); | |
1118 | // Throw exception if found. | |
1119 | throw new ExpectationException('"' . $column . '" with value "' . $value . '" is present in "' . | |
1120 | $row . '" row for table "' . $table . '"', $this->getSession() | |
1121 | ); | |
1122 | } catch (ElementNotFoundException $e) { | |
1123 | // Table row/column doesn't contain this value. Nothing to do. | |
1124 | continue; | |
1125 | } | |
1126 | } | |
1127 | } | |
1128 | } | |
786ea937 | 1129 | } |