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