[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 30 use Behat\Mink\Exception\ExpectationException as ExpectationException, 31 Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException, 32 Behat\Mink\Exception\DriverException as DriverException, 33 WebDriver\Exception\NoSuchElement as NoSuchElement, 34 WebDriver\Exception\StaleElementReference as StaleElementReference, 35 Behat\Gherkin\Node\TableNode as TableNode; 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 * @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 58 /** 59 * @var string when we want to check whether or not a new page has loaded, 60 * we first write this unique string into the page. Then later, by checking 61 * whether it is still there, we can tell if a new page has been loaded. 62 */ 63 const PAGE_LOAD_DETECTION_STRING = 'new_page_not_loaded_since_behat_started_watching'; 64 65 /** 66 * @var $pageloaddetectionrunning boolean Used to ensure that page load detection was started before a page reload 67 * was checked for. 68 */ 69 private $pageloaddetectionrunning = false; 70 71 /** 72 * Opens Moodle homepage. 73 * 74 * @Given /^I am on homepage$/ 75 */ 76 public function i_am_on_homepage() { 77 $this->getSession()->visit($this->locate_path('/')); 78 } 79 80 /** 81 * Opens Moodle site homepage. 82 * 83 * @Given /^I am on site homepage$/ 84 */ 85 public function i_am_on_site_homepage() { 86 $this->getSession()->visit($this->locate_path('/?redirect=0')); 87 } 88 89 /** 90 * Reloads the current page. 91 * 92 * @Given /^I reload the page$/ 93 */ 94 public function reload() { 95 $this->getSession()->reload(); 96 } 97 98 /** 99 * Follows the page redirection. Use this step after any action that shows a message and waits for a redirection 100 * 101 * @Given /^I wait to be redirected$/ 102 */ 103 public function i_wait_to_be_redirected() { 104 105 // Xpath and processes based on core_renderer::redirect_message(), core_renderer::$metarefreshtag and 106 // moodle_page::$periodicrefreshdelay possible values. 107 if (!$metarefresh = $this->getSession()->getPage()->find('xpath', "//head/descendant::meta[@http-equiv='refresh']")) { 108 // We don't fail the scenario if no redirection with message is found to avoid race condition false failures. 109 return true; 110 } 111 112 // Wrapped in try & catch in case the redirection has already been executed. 113 try { 114 $content = $metarefresh->getAttribute('content'); 115 } catch (NoSuchElement $e) { 116 return true; 117 } catch (StaleElementReference $e) { 118 return true; 119 } 120 121 // Getting the refresh time and the url if present. 122 if (strstr($content, 'url') != false) { 123 124 list($waittime, $url) = explode(';', $content); 125 126 // Cleaning the URL value. 127 $url = trim(substr($url, strpos($url, 'http'))); 128 129 } else { 130 // Just wait then. 131 $waittime = $content; 132 } 133 134 135 // Wait until the URL change is executed. 136 if ($this->running_javascript()) { 137 $this->getSession()->wait($waittime * 1000, false); 138 139 } else if (!empty($url)) { 140 // We redirect directly as we can not wait for an automatic redirection. 141 $this->getSession()->getDriver()->getClient()->request('get', $url); 142 143 } else { 144 // Reload the page if no URL was provided. 145 $this->getSession()->getDriver()->reload(); 146 } 147 } 148 149 /** 150 * Switches to the specified iframe. 151 * 152 * @Given /^I switch to "(?P<iframe_name_string>(?:[^"]|\\")*)" iframe$/ 153 * @param string $iframename 154 */ 155 public function switch_to_iframe($iframename) { 156 157 // We spin to give time to the iframe to be loaded. 158 // Using extended timeout as we don't know about which 159 // kind of iframe will be loaded. 160 $this->spin( 161 function($context, $iframename) { 162 $context->getSession()->switchToIFrame($iframename); 163 164 // If no exception we are done. 165 return true; 166 }, 167 $iframename, 168 self::EXTENDED_TIMEOUT 169 ); 170 } 171 172 /** 173 * Switches to the main Moodle frame. 174 * 175 * @Given /^I switch to the main frame$/ 176 */ 177 public function switch_to_the_main_frame() { 178 $this->getSession()->switchToIFrame(); 179 } 180 181 /** 182 * Switches to the specified window. Useful when interacting with popup windows. 183 * 184 * @Given /^I switch to "(?P<window_name_string>(?:[^"]|\\")*)" window$/ 185 * @param string $windowname 186 */ 187 public function switch_to_window($windowname) { 188 // In Behat, some browsers (e.g. Chrome) are unable to switch to a 189 // window without a name, and by default the main browser window does 190 // not have a name. To work-around this, when we switch away from an 191 // unnamed window (presumably the main window) to some other named 192 // window, then we first set the main window name to a conventional 193 // value that we can later use this name to switch back. 194 $this->getSession()->executeScript( 195 'if (window.name == "") window.name = "' . self::MAIN_WINDOW_NAME . '"'); 196 197 $this->getSession()->switchToWindow($windowname); 198 } 199 200 /** 201 * Switches to the main Moodle window. Useful when you finish interacting with popup windows. 202 * 203 * @Given /^I switch to the main window$/ 204 */ 205 public function switch_to_the_main_window() { 206 $this->getSession()->switchToWindow(self::MAIN_WINDOW_NAME); 207 } 208 209 /** 210 * Accepts the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental. 211 * @Given /^I accept the currently displayed dialog$/ 212 */ 213 public function accept_currently_displayed_alert_dialog() { 214 $this->getSession()->getDriver()->getWebDriverSession()->accept_alert(); 215 } 216 217 /** 218 * Dismisses the currently displayed alert dialog. This step does not work in all the browsers, consider it experimental. 219 * @Given /^I dismiss the currently displayed dialog$/ 220 */ 221 public function dismiss_currently_displayed_alert_dialog() { 222 $this->getSession()->getDriver()->getWebDriverSession()->dismiss_alert(); 223 } 224 225 /** 226 * Clicks link with specified id|title|alt|text. 227 * 228 * @When /^I follow "(?P<link_string>(?:[^"]|\\")*)"$/ 229 * @throws ElementNotFoundException Thrown by behat_base::find 230 * @param string $link 231 */ 232 public function click_link($link) { 233 234 $linknode = $this->find_link($link); 235 $this->ensure_node_is_visible($linknode); 236 $linknode->click(); 237 } 238 239 /** 240 * Waits X seconds. Required after an action that requires data from an AJAX request. 241 * 242 * @Then /^I wait "(?P<seconds_number>\d+)" seconds$/ 243 * @param int $seconds 244 */ 245 public function i_wait_seconds($seconds) { 246 if ($this->running_javascript()) { 247 $this->getSession()->wait($seconds * 1000, false); 248 } else { 249 sleep($seconds); 250 } 251 } 252 253 /** 254 * Waits until the page is completely loaded. This step is auto-executed after every step. 255 * 256 * @Given /^I wait until the page is ready$/ 257 */ 258 public function wait_until_the_page_is_ready() { 259 260 // No need to wait if not running JS. 261 if (!$this->running_javascript()) { 262 return; 263 } 264 265 $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS); 266 } 267 268 /** 269 * Waits until the provided element selector exists in the DOM 270 * 271 * Using the protected method as this method will be usually 272 * called by other methods which are not returning a set of 273 * steps and performs the actions directly, so it would not 274 * be executed if it returns another step. 275 276 * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" exists$/ 277 * @param string $element 278 * @param string $selector 279 * @return void 280 */ 281 public function wait_until_exists($element, $selectortype) { 282 $this->ensure_element_exists($element, $selectortype); 283 } 284 285 /** 286 * Waits until the provided element does not exist in the DOM 287 * 288 * Using the protected method as this method will be usually 289 * called by other methods which are not returning a set of 290 * steps and performs the actions directly, so it would not 291 * be executed if it returns another step. 292 293 * @Given /^I wait until "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" does not exist$/ 294 * @param string $element 295 * @param string $selector 296 * @return void 297 */ 298 public function wait_until_does_not_exists($element, $selectortype) { 299 $this->ensure_element_does_not_exist($element, $selectortype); 300 } 301 302 /** 303 * Generic mouse over action. Mouse over a element of the specified type. 304 * 305 * @When /^I hover "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ 306 * @param string $element Element we look for 307 * @param string $selectortype The type of what we look for 308 */ 309 public function i_hover($element, $selectortype) { 310 311 // Gets the node based on the requested selector type and locator. 312 $node = $this->get_selected_node($selectortype, $element); 313 $node->mouseOver(); 314 } 315 316 /** 317 * Generic click action. Click on the element of the specified type. 318 * 319 * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ 320 * @param string $element Element we look for 321 * @param string $selectortype The type of what we look for 322 */ 323 public function i_click_on($element, $selectortype) { 324 325 // Gets the node based on the requested selector type and locator. 326 $node = $this->get_selected_node($selectortype, $element); 327 $this->ensure_node_is_visible($node); 328 $node->click(); 329 } 330 331 /** 332 * Sets the focus and takes away the focus from an element, generating blur JS event. 333 * 334 * @When /^I take focus off "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ 335 * @param string $element Element we look for 336 * @param string $selectortype The type of what we look for 337 */ 338 public function i_take_focus_off_field($element, $selectortype) { 339 if (!$this->running_javascript()) { 340 throw new ExpectationException('Can\'t take focus off from "' . $element . '" in non-js mode', $this->getSession()); 341 } 342 // Gets the node based on the requested selector type and locator. 343 $node = $this->get_selected_node($selectortype, $element); 344 $this->ensure_node_is_visible($node); 345 346 // Ensure element is focused before taking it off. 347 $node->focus(); 348 $node->blur(); 349 } 350 351 /** 352 * Clicks the specified element and confirms the expected dialogue. 353 * 354 * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" confirming the dialogue$/ 355 * @throws ElementNotFoundException Thrown by behat_base::find 356 * @param string $element Element we look for 357 * @param string $selectortype The type of what we look for 358 */ 359 public function i_click_on_confirming_the_dialogue($element, $selectortype) { 360 $this->i_click_on($element, $selectortype); 361 $this->accept_currently_displayed_alert_dialog(); 362 } 363 364 /** 365 * Clicks the specified element and dismissing the expected dialogue. 366 * 367 * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" dismissing the dialogue$/ 368 * @throws ElementNotFoundException Thrown by behat_base::find 369 * @param string $element Element we look for 370 * @param string $selectortype The type of what we look for 371 */ 372 public function i_click_on_dismissing_the_dialogue($element, $selectortype) { 373 $this->i_click_on($element, $selectortype); 374 $this->dismiss_currently_displayed_alert_dialog(); 375 } 376 377 /** 378 * Click on the element of the specified type which is located inside the second element. 379 * 380 * @When /^I click on "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ 381 * @param string $element Element we look for 382 * @param string $selectortype The type of what we look for 383 * @param string $nodeelement Element we look in 384 * @param string $nodeselectortype The type of selector where we look in 385 */ 386 public function i_click_on_in_the($element, $selectortype, $nodeelement, $nodeselectortype) { 387 388 $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); 389 $this->ensure_node_is_visible($node); 390 $node->click(); 391 } 392 393 /** 394 * Drags and drops the specified element to the specified container. This step does not work in all the browsers, consider it experimental. 395 * 396 * The steps definitions calling this step as part of them should 397 * manage the wait times by themselves as the times and when the 398 * waits should be done depends on what is being dragged & dropper. 399 * 400 * @Given /^I drag "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" and I drop it in "(?P<container_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ 401 * @param string $element 402 * @param string $selectortype 403 * @param string $containerelement 404 * @param string $containerselectortype 405 */ 406 public function i_drag_and_i_drop_it_in($element, $selectortype, $containerelement, $containerselectortype) { 407 408 list($sourceselector, $sourcelocator) = $this->transform_selector($selectortype, $element); 409 $sourcexpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($sourceselector, $sourcelocator); 410 411 list($containerselector, $containerlocator) = $this->transform_selector($containerselectortype, $containerelement); 412 $destinationxpath = $this->getSession()->getSelectorsHandler()->selectorToXpath($containerselector, $containerlocator); 413 414 $node = $this->get_selected_node("xpath_element", $sourcexpath); 415 if (!$node->isVisible()) { 416 throw new ExpectationException('"' . $sourcexpath . '" "xpath_element" is not visible', $this->getSession()); 417 } 418 $node = $this->get_selected_node("xpath_element", $destinationxpath); 419 if (!$node->isVisible()) { 420 throw new ExpectationException('"' . $destinationxpath . '" "xpath_element" is not visible', $this->getSession()); 421 } 422 423 $this->getSession()->getDriver()->dragTo($sourcexpath, $destinationxpath); 424 } 425 426 /** 427 * Checks, that the specified element is visible. Only available in tests using Javascript. 428 * 429 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should be visible$/ 430 * @throws ElementNotFoundException 431 * @throws ExpectationException 432 * @throws DriverException 433 * @param string $element 434 * @param string $selectortype 435 * @return void 436 */ 437 public function should_be_visible($element, $selectortype) { 438 439 if (!$this->running_javascript()) { 440 throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); 441 } 442 443 $node = $this->get_selected_node($selectortype, $element); 444 if (!$node->isVisible()) { 445 throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is not visible', $this->getSession()); 446 } 447 } 448 449 /** 450 * Checks, that the existing element is not visible. Only available in tests using Javascript. 451 * 452 * As a "not" method, it's performance could not be good, but in this 453 * case the performance is good because the element must exist, 454 * otherwise there would be a ElementNotFoundException, also here we are 455 * not spinning until the element is visible. 456 * 457 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>(?:[^"]|\\")*)" should not be visible$/ 458 * @throws ElementNotFoundException 459 * @throws ExpectationException 460 * @param string $element 461 * @param string $selectortype 462 * @return void 463 */ 464 public function should_not_be_visible($element, $selectortype) { 465 466 try { 467 $this->should_be_visible($element, $selectortype); 468 } catch (ExpectationException $e) { 469 // All as expected. 470 return; 471 } 472 throw new ExpectationException('"' . $element . '" "' . $selectortype . '" is visible', $this->getSession()); 473 } 474 475 /** 476 * Checks, that the specified element is visible inside the specified container. Only available in tests using Javascript. 477 * 478 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should be visible$/ 479 * @throws ElementNotFoundException 480 * @throws DriverException 481 * @throws ExpectationException 482 * @param string $element Element we look for 483 * @param string $selectortype The type of what we look for 484 * @param string $nodeelement Element we look in 485 * @param string $nodeselectortype The type of selector where we look in 486 */ 487 public function in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { 488 489 if (!$this->running_javascript()) { 490 throw new DriverException('Visible checks are disabled in scenarios without Javascript support'); 491 } 492 493 $node = $this->get_node_in_container($selectortype, $element, $nodeselectortype, $nodeelement); 494 if (!$node->isVisible()) { 495 throw new ExpectationException( 496 '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is not visible', 497 $this->getSession() 498 ); 499 } 500 } 501 502 /** 503 * Checks, that the existing element is not visible inside the existing container. Only available in tests using Javascript. 504 * 505 * As a "not" method, it's performance could not be good, but in this 506 * case the performance is good because the element must exist, 507 * otherwise there would be a ElementNotFoundException, also here we are 508 * not spinning until the element is visible. 509 * 510 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" should not be visible$/ 511 * @throws ElementNotFoundException 512 * @throws ExpectationException 513 * @param string $element Element we look for 514 * @param string $selectortype The type of what we look for 515 * @param string $nodeelement Element we look in 516 * @param string $nodeselectortype The type of selector where we look in 517 */ 518 public function in_the_should_not_be_visible($element, $selectortype, $nodeelement, $nodeselectortype) { 519 520 try { 521 $this->in_the_should_be_visible($element, $selectortype, $nodeelement, $nodeselectortype); 522 } catch (ExpectationException $e) { 523 // All as expected. 524 return; 525 } 526 throw new ExpectationException( 527 '"' . $element . '" "' . $selectortype . '" in the "' . $nodeelement . '" "' . $nodeselectortype . '" is visible', 528 $this->getSession() 529 ); 530 } 531 532 /** 533 * Checks, that page contains specified text. It also checks if the text is visible when running Javascript tests. 534 * 535 * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)"$/ 536 * @throws ExpectationException 537 * @param string $text 538 */ 539 public function assert_page_contains_text($text) { 540 541 // Looking for all the matching nodes without any other descendant matching the 542 // same xpath (we are using contains(., ....). 543 $xpathliteral = behat_context_helper::escape($text); 544 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . 545 "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; 546 547 try { 548 $nodes = $this->find_all('xpath', $xpath); 549 } catch (ElementNotFoundException $e) { 550 throw new ExpectationException('"' . $text . '" text was not found in the page', $this->getSession()); 551 } 552 553 // If we are not running javascript we have enough with the 554 // element existing as we can't check if it is visible. 555 if (!$this->running_javascript()) { 556 return; 557 } 558 559 // We spin as we don't have enough checking that the element is there, we 560 // should also ensure that the element is visible. Using microsleep as this 561 // is a repeated step and global performance is important. 562 $this->spin( 563 function($context, $args) { 564 565 foreach ($args['nodes'] as $node) { 566 if ($node->isVisible()) { 567 return true; 568 } 569 } 570 571 // If non of the nodes is visible we loop again. 572 throw new ExpectationException('"' . $args['text'] . '" text was found but was not visible', $context->getSession()); 573 }, 574 array('nodes' => $nodes, 'text' => $text), 575 false, 576 false, 577 true 578 ); 579 580 } 581 582 /** 583 * Checks, that page doesn't contain specified text. When running Javascript tests it also considers that texts may be hidden. 584 * 585 * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)"$/ 586 * @throws ExpectationException 587 * @param string $text 588 */ 589 public function assert_page_not_contains_text($text) { 590 591 // Looking for all the matching nodes without any other descendant matching the 592 // same xpath (we are using contains(., ....). 593 $xpathliteral = behat_context_helper::escape($text); 594 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . 595 "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; 596 597 // We should wait a while to ensure that the page is not still loading elements. 598 // Waiting less than self::TIMEOUT as we already waited for the DOM to be ready and 599 // all JS to be executed. 600 try { 601 $nodes = $this->find_all('xpath', $xpath, false, false, self::REDUCED_TIMEOUT); 602 } catch (ElementNotFoundException $e) { 603 // All ok. 604 return; 605 } 606 607 // If we are not running javascript we have enough with the 608 // element existing as we can't check if it is hidden. 609 if (!$this->running_javascript()) { 610 throw new ExpectationException('"' . $text . '" text was found in the page', $this->getSession()); 611 } 612 613 // If the element is there we should be sure that it is not visible. 614 $this->spin( 615 function($context, $args) { 616 617 foreach ($args['nodes'] as $node) { 618 if ($node->isVisible()) { 619 throw new ExpectationException('"' . $args['text'] . '" text was found in the page', $context->getSession()); 620 } 621 } 622 623 // If non of the found nodes is visible we consider that the text is not visible. 624 return true; 625 }, 626 array('nodes' => $nodes, 'text' => $text), 627 self::REDUCED_TIMEOUT, 628 false, 629 true 630 ); 631 632 } 633 634 /** 635 * Checks, that the specified element contains the specified text. When running Javascript tests it also considers that texts may be hidden. 636 * 637 * @Then /^I should see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ 638 * @throws ElementNotFoundException 639 * @throws ExpectationException 640 * @param string $text 641 * @param string $element Element we look in. 642 * @param string $selectortype The type of element where we are looking in. 643 */ 644 public function assert_element_contains_text($text, $element, $selectortype) { 645 646 // Getting the container where the text should be found. 647 $container = $this->get_selected_node($selectortype, $element); 648 649 // Looking for all the matching nodes without any other descendant matching the 650 // same xpath (we are using contains(., ....). 651 $xpathliteral = behat_context_helper::escape($text); 652 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . 653 "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; 654 655 // Wait until it finds the text inside the container, otherwise custom exception. 656 try { 657 $nodes = $this->find_all('xpath', $xpath, false, $container); 658 } catch (ElementNotFoundException $e) { 659 throw new ExpectationException('"' . $text . '" text was not found in the "' . $element . '" element', $this->getSession()); 660 } 661 662 // If we are not running javascript we have enough with the 663 // element existing as we can't check if it is visible. 664 if (!$this->running_javascript()) { 665 return; 666 } 667 668 // We also check the element visibility when running JS tests. Using microsleep as this 669 // is a repeated step and global performance is important. 670 $this->spin( 671 function($context, $args) { 672 673 foreach ($args['nodes'] as $node) { 674 if ($node->isVisible()) { 675 return true; 676 } 677 } 678 679 throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element but was not visible', $context->getSession()); 680 }, 681 array('nodes' => $nodes, 'text' => $text, 'element' => $element), 682 false, 683 false, 684 true 685 ); 686 } 687 688 /** 689 * Checks, that the specified element does not contain the specified text. When running Javascript tests it also considers that texts may be hidden. 690 * 691 * @Then /^I should not see "(?P<text_string>(?:[^"]|\\")*)" in the "(?P<element_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)"$/ 692 * @throws ElementNotFoundException 693 * @throws ExpectationException 694 * @param string $text 695 * @param string $element Element we look in. 696 * @param string $selectortype The type of element where we are looking in. 697 */ 698 public function assert_element_not_contains_text($text, $element, $selectortype) { 699 700 // Getting the container where the text should be found. 701 $container = $this->get_selected_node($selectortype, $element); 702 703 // Looking for all the matching nodes without any other descendant matching the 704 // same xpath (we are using contains(., ....). 705 $xpathliteral = behat_context_helper::escape($text); 706 $xpath = "/descendant-or-self::*[contains(., $xpathliteral)]" . 707 "[count(descendant::*[contains(., $xpathliteral)]) = 0]"; 708 709 // We should wait a while to ensure that the page is not still loading elements. 710 // Giving preference to the reliability of the results rather than to the performance. 711 try { 712 $nodes = $this->find_all('xpath', $xpath, false, $container, self::REDUCED_TIMEOUT); 713 } catch (ElementNotFoundException $e) { 714 // All ok. 715 return; 716 } 717 718 // If we are not running javascript we have enough with the 719 // element not being found as we can't check if it is visible. 720 if (!$this->running_javascript()) { 721 throw new ExpectationException('"' . $text . '" text was found in the "' . $element . '" element', $this->getSession()); 722 } 723 724 // We need to ensure all the found nodes are hidden. 725 $this->spin( 726 function($context, $args) { 727 728 foreach ($args['nodes'] as $node) { 729 if ($node->isVisible()) { 730 throw new ExpectationException('"' . $args['text'] . '" text was found in the "' . $args['element'] . '" element', $context->getSession()); 731 } 732 } 733 734 // If all the found nodes are hidden we are happy. 735 return true; 736 }, 737 array('nodes' => $nodes, 'text' => $text, 'element' => $element), 738 self::REDUCED_TIMEOUT, 739 false, 740 true 741 ); 742 } 743 744 /** 745 * Checks, that the first specified element appears before the second one. 746 * 747 * @Given /^"(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear before "(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ 748 * @throws ExpectationException 749 * @param string $preelement The locator of the preceding element 750 * @param string $preselectortype The locator of the preceding element 751 * @param string $postelement The locator of the latest element 752 * @param string $postselectortype The selector type of the latest element 753 */ 754 public function should_appear_before($preelement, $preselectortype, $postelement, $postselectortype) { 755 756 // We allow postselectortype as a non-text based selector. 757 list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); 758 list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); 759 760 $prexpath = $this->find($preselector, $prelocator)->getXpath(); 761 $postxpath = $this->find($postselector, $postlocator)->getXpath(); 762 763 // Using following xpath axe to find it. 764 $msg = '"'.$preelement.'" "'.$preselectortype.'" does not appear before "'.$postelement.'" "'.$postselectortype.'"'; 765 $xpath = $prexpath.'/following::*[contains(., '.$postxpath.')]'; 766 if (!$this->getSession()->getDriver()->find($xpath)) { 767 throw new ExpectationException($msg, $this->getSession()); 768 } 769 } 770 771 /** 772 * Checks, that the first specified element appears after the second one. 773 * 774 * @Given /^"(?P<following_element_string>(?:[^"]|\\")*)" "(?P<selector1_string>(?:[^"]|\\")*)" should appear after "(?P<preceding_element_string>(?:[^"]|\\")*)" "(?P<selector2_string>(?:[^"]|\\")*)"$/ 775 * @throws ExpectationException 776 * @param string $postelement The locator of the latest element 777 * @param string $postselectortype The selector type of the latest element 778 * @param string $preelement The locator of the preceding element 779 * @param string $preselectortype The locator of the preceding element 780 */ 781 public function should_appear_after($postelement, $postselectortype, $preelement, $preselectortype) { 782 783 // We allow postselectortype as a non-text based selector. 784 list($postselector, $postlocator) = $this->transform_selector($postselectortype, $postelement); 785 list($preselector, $prelocator) = $this->transform_selector($preselectortype, $preelement); 786 787 $postxpath = $this->find($postselector, $postlocator)->getXpath(); 788 $prexpath = $this->find($preselector, $prelocator)->getXpath(); 789 790 // Using preceding xpath axe to find it. 791 $msg = '"'.$postelement.'" "'.$postselectortype.'" does not appear after "'.$preelement.'" "'.$preselectortype.'"'; 792 $xpath = $postxpath.'/preceding::*[contains(., '.$prexpath.')]'; 793 if (!$this->getSession()->getDriver()->find($xpath)) { 794 throw new ExpectationException($msg, $this->getSession()); 795 } 796 } 797 798 /** 799 * Checks, that element of specified type is disabled. 800 * 801 * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be disabled$/ 802 * @throws ExpectationException Thrown by behat_base::find 803 * @param string $element Element we look in 804 * @param string $selectortype The type of element where we are looking in. 805 */ 806 public function the_element_should_be_disabled($element, $selectortype) { 807 808 // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. 809 $node = $this->get_selected_node($selectortype, $element); 810 811 if (!$node->hasAttribute('disabled')) { 812 throw new ExpectationException('The element "' . $element . '" is not disabled', $this->getSession()); 813 } 814 } 815 816 /** 817 * Checks, that element of specified type is enabled. 818 * 819 * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be enabled$/ 820 * @throws ExpectationException Thrown by behat_base::find 821 * @param string $element Element we look on 822 * @param string $selectortype The type of where we look 823 */ 824 public function the_element_should_be_enabled($element, $selectortype) { 825 826 // Transforming from steps definitions selector/locator format to mink format and getting the NodeElement. 827 $node = $this->get_selected_node($selectortype, $element); 828 829 if ($node->hasAttribute('disabled')) { 830 throw new ExpectationException('The element "' . $element . '" is not enabled', $this->getSession()); 831 } 832 } 833 834 /** 835 * Checks the provided element and selector type are readonly on the current page. 836 * 837 * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should be readonly$/ 838 * @throws ExpectationException Thrown by behat_base::find 839 * @param string $element Element we look in 840 * @param string $selectortype The type of element where we are looking in. 841 */ 842 public function the_element_should_be_readonly($element, $selectortype) { 843 // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. 844 $node = $this->get_selected_node($selectortype, $element); 845 846 if (!$node->hasAttribute('readonly')) { 847 throw new ExpectationException('The element "' . $element . '" is not readonly', $this->getSession()); 848 } 849 } 850 851 /** 852 * Checks the provided element and selector type are not readonly on the current page. 853 * 854 * @Then /^the "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not be readonly$/ 855 * @throws ExpectationException Thrown by behat_base::find 856 * @param string $element Element we look in 857 * @param string $selectortype The type of element where we are looking in. 858 */ 859 public function the_element_should_not_be_readonly($element, $selectortype) { 860 // Transforming from steps definitions selector/locator format to Mink format and getting the NodeElement. 861 $node = $this->get_selected_node($selectortype, $element); 862 863 if ($node->hasAttribute('readonly')) { 864 throw new ExpectationException('The element "' . $element . '" is readonly', $this->getSession()); 865 } 866 } 867 868 /** 869 * Checks the provided element and selector type exists in the current page. 870 * 871 * This step is for advanced users, use it if you don't find anything else suitable for what you need. 872 * 873 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist$/ 874 * @throws ElementNotFoundException Thrown by behat_base::find 875 * @param string $element The locator of the specified selector 876 * @param string $selectortype The selector type 877 */ 878 public function should_exist($element, $selectortype) { 879 880 // Getting Mink selector and locator. 881 list($selector, $locator) = $this->transform_selector($selectortype, $element); 882 883 // Will throw an ElementNotFoundException if it does not exist. 884 $this->find($selector, $locator); 885 } 886 887 /** 888 * Checks that the provided element and selector type not exists in the current page. 889 * 890 * This step is for advanced users, use it if you don't find anything else suitable for what you need. 891 * 892 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist$/ 893 * @throws ExpectationException 894 * @param string $element The locator of the specified selector 895 * @param string $selectortype The selector type 896 */ 897 public function should_not_exist($element, $selectortype) { 898 899 // Getting Mink selector and locator. 900 list($selector, $locator) = $this->transform_selector($selectortype, $element); 901 902 try { 903 904 // Using directly the spin method as we want a reduced timeout but there is no 905 // need for a 0.1 seconds interval because in the optimistic case we will timeout. 906 $params = array('selector' => $selector, 'locator' => $locator); 907 // The exception does not really matter as we will catch it and will never "explode". 908 $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $element); 909 910 // If all goes good it will throw an ElementNotFoundExceptionn that we will catch. 911 $this->spin( 912 function($context, $args) { 913 return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']); 914 }, 915 $params, 916 self::REDUCED_TIMEOUT, 917 $exception, 918 false 919 ); 920 } catch (ElementNotFoundException $e) { 921 // It passes. 922 return; 923 } 924 925 throw new ExpectationException('The "' . $element . '" "' . $selectortype . 926 '" exists in the current page', $this->getSession()); 927 } 928 929 /** 930 * This step triggers cron like a user would do going to admin/cron.php. 931 * 932 * @Given /^I trigger cron$/ 933 */ 934 public function i_trigger_cron() { 935 $this->getSession()->visit($this->locate_path('/admin/cron.php')); 936 } 937 938 /** 939 * Runs a scheduled task immediately, given full class name. 940 * 941 * This is faster and more reliable than running cron (running cron won't 942 * work more than once in the same test, for instance). However it is 943 * a little less 'realistic'. 944 * 945 * While the task is running, we suppress mtrace output because it makes 946 * the Behat result look ugly. 947 * 948 * Note: Most of the code relating to running a task is based on 949 * admin/tool/task/cli/schedule_task.php. 950 * 951 * @Given /^I run the scheduled task "(?P<task_name>[^"]+)"$/ 952 * @param string $taskname Name of task e.g. 'mod_whatever\task\do_something' 953 */ 954 public function i_run_the_scheduled_task($taskname) { 955 $task = \core\task\manager::get_scheduled_task($taskname); 956 if (!$task) { 957 throw new DriverException('The "' . $taskname . '" scheduled task does not exist'); 958 } 959 960 // Do setup for cron task. 961 raise_memory_limit(MEMORY_EXTRA); 962 cron_setup_user(); 963 964 // Get lock. 965 $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron'); 966 if (!$cronlock = $cronlockfactory->get_lock('core_cron', 10)) { 967 throw new DriverException('Unable to obtain core_cron lock for scheduled task'); 968 } 969 if (!$lock = $cronlockfactory->get_lock('\\' . get_class($task), 10)) { 970 $cronlock->release(); 971 throw new DriverException('Unable to obtain task lock for scheduled task'); 972 } 973 $task->set_lock($lock); 974 if (!$task->is_blocking()) { 975 $cronlock->release(); 976 } else { 977 $task->set_cron_lock($cronlock); 978 } 979 980 try { 981 // Discard task output as not appropriate for Behat output! 982 ob_start(); 983 $task->execute(); 984 ob_end_clean(); 985 986 // Mark task complete. 987 \core\task\manager::scheduled_task_complete($task); 988 } catch (Exception $e) { 989 // Mark task failed and throw exception. 990 \core\task\manager::scheduled_task_failed($task); 991 throw new DriverException('The "' . $taskname . '" scheduled task failed', 0, $e); 992 } 993 } 994 995 /** 996 * Checks that an element and selector type exists in another element and selector type on the current page. 997 * 998 * This step is for advanced users, use it if you don't find anything else suitable for what you need. 999 * 1000 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ 1001 * @throws ElementNotFoundException Thrown by behat_base::find 1002 * @param string $element The locator of the specified selector 1003 * @param string $selectortype The selector type 1004 * @param string $containerelement The container selector type 1005 * @param string $containerselectortype The container locator 1006 */ 1007 public function should_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { 1008 // Get the container node. 1009 $containernode = $this->get_selected_node($containerselectortype, $containerelement); 1010 1011 list($selector, $locator) = $this->transform_selector($selectortype, $element); 1012 1013 // Specific exception giving info about where can't we find the element. 1014 $locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"'; 1015 $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg); 1016 1017 // Looks for the requested node inside the container node. 1018 $this->find($selector, $locator, $exception, $containernode); 1019 } 1020 1021 /** 1022 * Checks that an element and selector type does not exist in another element and selector type on the current page. 1023 * 1024 * This step is for advanced users, use it if you don't find anything else suitable for what you need. 1025 * 1026 * @Then /^"(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not exist in the "(?P<element2_string>(?:[^"]|\\")*)" "(?P<selector2_string>[^"]*)"$/ 1027 * @throws ExpectationException 1028 * @param string $element The locator of the specified selector 1029 * @param string $selectortype The selector type 1030 * @param string $containerelement The container selector type 1031 * @param string $containerselectortype The container locator 1032 */ 1033 public function should_not_exist_in_the($element, $selectortype, $containerelement, $containerselectortype) { 1034 1035 // Get the container node; here we throw an exception 1036 // if the container node does not exist. 1037 $containernode = $this->get_selected_node($containerselectortype, $containerelement); 1038 1039 list($selector, $locator) = $this->transform_selector($selectortype, $element); 1040 1041 // Will throw an ElementNotFoundException if it does not exist, but, actually 1042 // it should not exist, so we try & catch it. 1043 try { 1044 // Would be better to use a 1 second sleep because the element should not be there, 1045 // but we would need to duplicate the whole find_all() logic to do it, the benefit of 1046 // changing to 1 second sleep is not significant. 1047 $this->find($selector, $locator, false, $containernode, self::REDUCED_TIMEOUT); 1048 } catch (ElementNotFoundException $e) { 1049 // It passes. 1050 return; 1051 } 1052 throw new ExpectationException('The "' . $element . '" "' . $selectortype . '" exists in the "' . 1053 $containerelement . '" "' . $containerselectortype . '"', $this->getSession()); 1054 } 1055 1056 /** 1057 * Change browser window size small: 640x480, medium: 1024x768, large: 2560x1600, custom: widthxheight 1058 * 1059 * Example: I change window size to "small" or I change window size to "1024x768" 1060 * or I change viewport size to "800x600". The viewport option is useful to guarantee that the 1061 * browser window has same viewport size even when you run Behat on multiple operating systems. 1062 * 1063 * @throws ExpectationException 1064 * @Then /^I change (window|viewport) size to "(small|medium|large|\d+x\d+)"$/ 1065 * @param string $windowsize size of the window (small|medium|large|wxh). 1066 */ 1067 public function i_change_window_size_to($windowviewport, $windowsize) { 1068 $this->resize_window($windowsize, $windowviewport === 'viewport'); 1069 } 1070 1071 /** 1072 * Checks whether there is an attribute on the given element that contains the specified text. 1073 * 1074 * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should contain "(?P<text_string>(?:[^"]|\\")*)"$/ 1075 * @throws ExpectationException 1076 * @param string $attribute Name of attribute 1077 * @param string $element The locator of the specified selector 1078 * @param string $selectortype The selector type 1079 * @param string $text Expected substring 1080 */ 1081 public function the_attribute_of_should_contain($attribute, $element, $selectortype, $text) { 1082 // Get the container node (exception if it doesn't exist). 1083 $containernode = $this->get_selected_node($selectortype, $element); 1084 $value = $containernode->getAttribute($attribute); 1085 if ($value == null) { 1086 throw new ExpectationException('The attribute "' . $attribute. '" does not exist', 1087 $this->getSession()); 1088 } else if (strpos($value, $text) === false) { 1089 throw new ExpectationException('The attribute "' . $attribute . 1090 '" does not contain "' . $text . '" (actual value: "' . $value . '")', 1091 $this->getSession()); 1092 } 1093 } 1094 1095 /** 1096 * Checks that the attribute on the given element does not contain the specified text. 1097 * 1098 * @Then /^the "(?P<attribute_string>[^"]*)" attribute of "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)" should not contain "(?P<text_string>(?:[^"]|\\")*)"$/ 1099 * @throws ExpectationException 1100 * @param string $attribute Name of attribute 1101 * @param string $element The locator of the specified selector 1102 * @param string $selectortype The selector type 1103 * @param string $text Expected substring 1104 */ 1105 public function the_attribute_of_should_not_contain($attribute, $element, $selectortype, $text) { 1106 // Get the container node (exception if it doesn't exist). 1107 $containernode = $this->get_selected_node($selectortype, $element); 1108 $value = $containernode->getAttribute($attribute); 1109 if ($value == null) { 1110 throw new ExpectationException('The attribute "' . $attribute. '" does not exist', 1111 $this->getSession()); 1112 } else if (strpos($value, $text) !== false) { 1113 throw new ExpectationException('The attribute "' . $attribute . 1114 '" contains "' . $text . '" (value: "' . $value . '")', 1115 $this->getSession()); 1116 } 1117 } 1118 1119 /** 1120 * Checks the provided value exists in specific row/column of table. 1121 * 1122 * @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should contain "(?P<value_string>[^"]*)"$/ 1123 * @throws ElementNotFoundException 1124 * @param string $row row text which will be looked in. 1125 * @param string $column column text to search (or numeric value for the column position) 1126 * @param string $table table id/class/caption 1127 * @param string $value text to check. 1128 */ 1129 public function row_column_of_table_should_contain($row, $column, $table, $value) { 1130 $tablenode = $this->get_selected_node('table', $table); 1131 $tablexpath = $tablenode->getXpath(); 1132 1133 $rowliteral = behat_context_helper::escape($row); 1134 $valueliteral = behat_context_helper::escape($value); 1135 $columnliteral = behat_context_helper::escape($column); 1136 1137 if (preg_match('/^-?(\d+)-?$/', $column, $columnasnumber)) { 1138 // Column indicated as a number, just use it as position of the column. 1139 $columnpositionxpath = "/child::*[position() = {$columnasnumber[1]}]"; 1140 } else { 1141 // Header can be in thead or tbody (first row), following xpath should work. 1142 $theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" . 1143 $columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]"; 1144 $tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" . 1145 $columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]"; 1146 1147 // Check if column exists. 1148 $columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]"; 1149 $columnheader = $this->getSession()->getDriver()->find($columnheaderxpath); 1150 if (empty($columnheader)) { 1151 $columnexceptionmsg = $column . '" in table "' . $table . '"'; 1152 throw new ElementNotFoundException($this->getSession(), "\n$columnheaderxpath\n\n".'Column', null, $columnexceptionmsg); 1153 } 1154 // Following conditions were considered before finding column count. 1155 // 1. Table header can be in thead/tr/th or tbody/tr/td[1]. 1156 // 2. First column can have th (Gradebook -> user report), so having lenient sibling check. 1157 $columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath . 1158 "/preceding-sibling::*) + 1]"; 1159 } 1160 1161 // Check if value exists in specific row/column. 1162 // Get row xpath. 1163 // GoutteDriver uses DomCrawler\Crawler and it is making XPath relative to the current context, so use descendant. 1164 $rowxpath = $tablexpath."/tbody/tr[descendant::th[normalize-space(.)=" . $rowliteral . 1165 "] | descendant::td[normalize-space(.)=" . $rowliteral . "]]"; 1166 1167 $columnvaluexpath = $rowxpath . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]"; 1168 1169 // Looks for the requested node inside the container node. 1170 $coumnnode = $this->getSession()->getDriver()->find($columnvaluexpath); 1171 if (empty($coumnnode)) { 1172 $locatorexceptionmsg = $value . '" in "' . $row . '" row with column "' . $column; 1173 throw new ElementNotFoundException($this->getSession(), "\n$columnvaluexpath\n\n".'Column value', null, $locatorexceptionmsg); 1174 } 1175 } 1176 1177 /** 1178 * Checks the provided value should not exist in specific row/column of table. 1179 * 1180 * @Then /^"(?P<row_string>[^"]*)" row "(?P<column_string>[^"]*)" column of "(?P<table_string>[^"]*)" table should not contain "(?P<value_string>[^"]*)"$/ 1181 * @throws ElementNotFoundException 1182 * @param string $row row text which will be looked in. 1183 * @param string $column column text to search 1184 * @param string $table table id/class/caption 1185 * @param string $value text to check. 1186 */ 1187 public function row_column_of_table_should_not_contain($row, $column, $table, $value) { 1188 try { 1189 $this->row_column_of_table_should_contain($row, $column, $table, $value); 1190 } catch (ElementNotFoundException $e) { 1191 // Table row/column doesn't contain this value. Nothing to do. 1192 return; 1193 } 1194 // Throw exception if found. 1195 throw new ExpectationException( 1196 '"' . $column . '" with value "' . $value . '" is present in "' . $row . '" row for table "' . $table . '"', 1197 $this->getSession() 1198 ); 1199 } 1200 1201 /** 1202 * Checks that the provided value exist in table. 1203 * More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps. 1204 * 1205 * First row may contain column headers or numeric indexes of the columns 1206 * (syntax -1- is also considered to be column index). Column indexes are 1207 * useful in case of multirow headers and/or presence of cells with colspan. 1208 * 1209 * @Then /^the following should exist in the "(?P<table_string>[^"]*)" table:$/ 1210 * @throws ExpectationException 1211 * @param string $table name of table 1212 * @param TableNode $data table with first row as header and following values 1213 * | Header 1 | Header 2 | Header 3 | 1214 * | Value 1 | Value 2 | Value 3| 1215 */ 1216 public function following_should_exist_in_the_table($table, TableNode $data) { 1217 $datahash = $data->getHash(); 1218 1219 foreach ($datahash as $row) { 1220 $firstcell = null; 1221 foreach ($row as $column => $value) { 1222 if ($firstcell === null) { 1223 $firstcell = $value; 1224 } else { 1225 $this->row_column_of_table_should_contain($firstcell, $column, $table, $value); 1226 } 1227 } 1228 } 1229 } 1230 1231 /** 1232 * Checks that the provided value exist in table. 1233 * More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps. 1234 * 1235 * @Then /^the following should not exist in the "(?P<table_string>[^"]*)" table:$/ 1236 * @throws ExpectationException 1237 * @param string $table name of table 1238 * @param TableNode $data table with first row as header and following values 1239 * | Header 1 | Header 2 | Header 3 | 1240 * | Value 1 | Value 2 | Value 3| 1241 */ 1242 public function following_should_not_exist_in_the_table($table, TableNode $data) { 1243 $datahash = $data->getHash(); 1244 1245 foreach ($datahash as $value) { 1246 $row = array_shift($value); 1247 foreach ($value as $column => $value) { 1248 try { 1249 $this->row_column_of_table_should_contain($row, $column, $table, $value); 1250 // Throw exception if found. 1251 } catch (ElementNotFoundException $e) { 1252 // Table row/column doesn't contain this value. Nothing to do. 1253 continue; 1254 } 1255 throw new ExpectationException('"' . $column . '" with value "' . $value . '" is present in "' . 1256 $row . '" row for table "' . $table . '"', $this->getSession() 1257 ); 1258 } 1259 } 1260 } 1261 1262 /** 1263 * Given the text of a link, download the linked file and return the contents. 1264 * 1265 * This is a helper method used by {@link following_should_download_bytes()} 1266 * and {@link following_should_download_between_and_bytes()} 1267 * 1268 * @param string $link the text of the link. 1269 * @return string the content of the downloaded file. 1270 */ 1271 public function download_file_from_link($link) { 1272 // Find the link. 1273 $linknode = $this->find_link($link); 1274 $this->ensure_node_is_visible($linknode); 1275 1276 // Get the href and check it. 1277 $url = $linknode->getAttribute('href'); 1278 if (!$url) { 1279 throw new ExpectationException('Download link does not have href attribute', 1280 $this->getSession()); 1281 } 1282 if (!preg_match('~^https?://~', $url)) { 1283 throw new ExpectationException('Download link not an absolute URL: ' . $url, 1284 $this->getSession()); 1285 } 1286 1287 // Download the URL and check the size. 1288 $session = $this->getSession()->getCookie('MoodleSession'); 1289 return download_file_content($url, array('Cookie' => 'MoodleSession=' . $session)); 1290 } 1291 1292 /** 1293 * Downloads the file from a link on the page and checks the size. 1294 * 1295 * Only works if the link has an href attribute. Javascript downloads are 1296 * not supported. Currently, the href must be an absolute URL. 1297 * 1298 * @Then /^following "(?P<link_string>[^"]*)" should download "(?P<expected_bytes>\d+)" bytes$/ 1299 * @throws ExpectationException 1300 * @param string $link the text of the link. 1301 * @param number $expectedsize the expected file size in bytes. 1302 */ 1303 public function following_should_download_bytes($link, $expectedsize) { 1304 $exception = new ExpectationException('Error while downloading data from ' . $link, $this->getSession()); 1305 1306 // It will stop spinning once file is downloaded or time out. 1307 $result = $this->spin( 1308 function($context, $args) { 1309 $link = $args['link']; 1310 return $this->download_file_from_link($link); 1311 }, 1312 array('link' => $link), 1313 self::EXTENDED_TIMEOUT, 1314 $exception 1315 ); 1316 1317 // Check download size. 1318 $actualsize = (int)strlen($result); 1319 if ($actualsize !== (int)$expectedsize) { 1320 throw new ExpectationException('Downloaded data was ' . $actualsize . 1321 ' bytes, expecting ' . $expectedsize, $this->getSession()); 1322 } 1323 } 1324 1325 /** 1326 * Downloads the file from a link on the page and checks the size is in a given range. 1327 * 1328 * Only works if the link has an href attribute. Javascript downloads are 1329 * not supported. Currently, the href must be an absolute URL. 1330 * 1331 * The range includes the endpoints. That is, a 10 byte file in considered to 1332 * be between "5" and "10" bytes, and between "10" and "20" bytes. 1333 * 1334 * @Then /^following "(?P<link_string>[^"]*)" should download between "(?P<min_bytes>\d+)" and "(?P<max_bytes>\d+)" bytes$/ 1335 * @throws ExpectationException 1336 * @param string $link the text of the link. 1337 * @param number $minexpectedsize the minimum expected file size in bytes. 1338 * @param number $maxexpectedsize the maximum expected file size in bytes. 1339 */ 1340 public function following_should_download_between_and_bytes($link, $minexpectedsize, $maxexpectedsize) { 1341 // If the minimum is greater than the maximum then swap the values. 1342 if ((int)$minexpectedsize > (int)$maxexpectedsize) { 1343 list($minexpectedsize, $maxexpectedsize) = array($maxexpectedsize, $minexpectedsize); 1344 } 1345 1346 $exception = new ExpectationException('Error while downloading data from ' . $link, $this->getSession()); 1347 1348 // It will stop spinning once file is downloaded or time out. 1349 $result = $this->spin( 1350 function($context, $args) { 1351 $link = $args['link']; 1352 1353 return $this->download_file_from_link($link); 1354 }, 1355 array('link' => $link), 1356 self::EXTENDED_TIMEOUT, 1357 $exception 1358 ); 1359 1360 // Check download size. 1361 $actualsize = (int)strlen($result); 1362 if ($actualsize < $minexpectedsize || $actualsize > $maxexpectedsize) { 1363 throw new ExpectationException('Downloaded data was ' . $actualsize . 1364 ' bytes, expecting between ' . $minexpectedsize . ' and ' . 1365 $maxexpectedsize, $this->getSession()); 1366 } 1367 } 1368 1369 /** 1370 * Prepare to detect whether or not a new page has loaded (or the same page reloaded) some time in the future. 1371 * 1372 * @Given /^I start watching to see if a new page loads$/ 1373 */ 1374 public function i_start_watching_to_see_if_a_new_page_loads() { 1375 if (!$this->running_javascript()) { 1376 throw new DriverException('Page load detection requires JavaScript.'); 1377 } 1378 1379 $session = $this->getSession(); 1380 1381 if ($this->pageloaddetectionrunning || $session->getPage()->find('xpath', $this->get_page_load_xpath())) { 1382 // If we find this node at this point we are already watching for a reload and the behat steps 1383 // are out of order. We will treat this as an error - really it needs to be fixed as it indicates a problem. 1384 throw new ExpectationException( 1385 'Page load expectation error: page reloads are already been watched for.', $session); 1386 } 1387 1388 $this->pageloaddetectionrunning = true; 1389 1390 $session->executeScript( 1391 'var span = document.createElement("span"); 1392 span.setAttribute("data-rel", "' . self::PAGE_LOAD_DETECTION_STRING . '"); 1393 span.setAttribute("style", "display: none;"); 1394 document.body.appendChild(span);'); 1395 } 1396 1397 /** 1398 * Verify that a new page has loaded (or the same page has reloaded) since the 1399 * last "I start watching to see if a new page loads" step. 1400 * 1401 * @Given /^a new page should have loaded since I started watching$/ 1402 */ 1403 public function a_new_page_should_have_loaded_since_i_started_watching() { 1404 $session = $this->getSession(); 1405 1406 // Make sure page load tracking was started. 1407 if (!$this->pageloaddetectionrunning) { 1408 throw new ExpectationException( 1409 'Page load expectation error: page load tracking was not started.', $session); 1410 } 1411 1412 // As the node is inserted by code above it is either there or not, and we do not need spin and it is safe 1413 // to use the native API here which is great as exception handling (the alternative is slow). 1414 if ($session->getPage()->find('xpath', $this->get_page_load_xpath())) { 1415 // We don't want to find this node, if we do we have an error. 1416 throw new ExpectationException( 1417 'Page load expectation error: a new page has not been loaded when it should have been.', $session); 1418 } 1419 1420 // Cancel the tracking of pageloaddetectionrunning. 1421 $this->pageloaddetectionrunning = false; 1422 } 1423 1424 /** 1425 * Verify that a new page has not loaded (or the same page has reloaded) since the 1426 * last "I start watching to see if a new page loads" step. 1427 * 1428 * @Given /^a new page should not have loaded since I started watching$/ 1429 */ 1430 public function a_new_page_should_not_have_loaded_since_i_started_watching() { 1431 $session = $this->getSession(); 1432 1433 // Make sure page load tracking was started. 1434 if (!$this->pageloaddetectionrunning) { 1435 throw new ExpectationException( 1436 'Page load expectation error: page load tracking was not started.', $session); 1437 } 1438 1439 // We use our API here as we can use the exception handling provided by it. 1440 $this->find( 1441 'xpath', 1442 $this->get_page_load_xpath(), 1443 new ExpectationException( 1444 'Page load expectation error: A new page has been loaded when it should not have been.', 1445 $this->getSession() 1446 ) 1447 ); 1448 } 1449 1450 /** 1451 * Helper used by {@link a_new_page_should_have_loaded_since_i_started_watching} 1452 * and {@link a_new_page_should_not_have_loaded_since_i_started_watching} 1453 * @return string xpath expression. 1454 */ 1455 protected function get_page_load_xpath() { 1456 return "//span[@data-rel = '" . self::PAGE_LOAD_DETECTION_STRING . "']"; 1457 } 1458 1459 /** 1460 * Wait unit user press Enter/Return key. Useful when debugging a scenario. 1461 * 1462 * @Then /^(?:|I )pause(?:| scenario execution)$/ 1463 */ 1464 public function i_pause_scenario_executon() { 1465 global $CFG; 1466 1467 $posixexists = function_exists('posix_isatty'); 1468 1469 // Make sure this step is only used with interactive terminal (if detected). 1470 if ($posixexists && !@posix_isatty(STDOUT)) { 1471 $session = $this->getSession(); 1472 throw new ExpectationException('Break point should only be used with interative terminal.', $session); 1473 } 1474 1475 // Windows don't support ANSI code by default, but with ANSICON. 1476 $isansicon = getenv('ANSICON'); 1477 if (($CFG->ostype === 'WINDOWS') && empty($isansicon)) { 1478 fwrite(STDOUT, "Paused. Press Enter/Return to continue."); 1479 fread(STDIN, 1024); 1480 } else { 1481 fwrite(STDOUT, "\033[s\n\033[0;93mPaused. Press \033[1;31mEnter/Return\033[0;93m to continue.\033[0m"); 1482 fread(STDIN, 1024); 1483 fwrite(STDOUT, "\033[2A\033[u\033[2B"); 1484 } 1485 } 1486 1487 /** 1488 * Presses a given button in the browser. 1489 * NOTE: Phantomjs and goutte driver reloads page while navigating back and forward. 1490 * 1491 * @Then /^I press the "(back|forward|reload)" button in the browser$/ 1492 * @param string $button the button to press. 1493 * @throws ExpectationException 1494 */ 1495 public function i_press_in_the_browser($button) { 1496 $session = $this->getSession(); 1497 1498 if ($button == 'back') { 1499 $session->back(); 1500 } else if ($button == 'forward') { 1501 $session->forward(); 1502 } else if ($button == 'reload') { 1503 $session->reload(); 1504 } else { 1505 throw new ExpectationException('Unknown browser button.', $session); 1506 } 1507 } 1508 1509 /** 1510 * Trigger a keydown event for a key on a specific element. 1511 * 1512 * @When /^I press key "(?P<key_string>(?:[^"]|\\")*)" in "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ 1513 * @param string $key either char-code or character itself, 1514 * may optionally be prefixed with ctrl-, alt-, shift- or meta- 1515 * @param string $element Element we look for 1516 * @param string $selectortype The type of what we look for 1517 * @throws DriverException 1518 * @throws ExpectationException 1519 */ 1520 public function i_press_key_in_element($key, $element, $selectortype) { 1521 if (!$this->running_javascript()) { 1522 throw new DriverException('Key down step is not available with Javascript disabled'); 1523 } 1524 // Gets the node based on the requested selector type and locator. 1525 $node = $this->get_selected_node($selectortype, $element); 1526 $modifier = null; 1527 $validmodifiers = array('ctrl', 'alt', 'shift', 'meta'); 1528 $char = $key; 1529 if (strpos($key, '-')) { 1530 list($modifier, $char) = preg_split('/-/', $key, 2); 1531 $modifier = strtolower($modifier); 1532 if (!in_array($modifier, $validmodifiers)) { 1533 throw new ExpectationException(sprintf('Unknown key modifier: %s.', $modifier)); 1534 } 1535 } 1536 if (is_numeric($char)) { 1537 $char = (int)$char; 1538 } 1539 1540 $node->keyDown($char, $modifier); 1541 $node->keyPress($char, $modifier); 1542 $node->keyUp($char, $modifier); 1543 } 1544 1545 /** 1546 * Press tab key on a specific element. 1547 * 1548 * @When /^I press tab key in "(?P<element_string>(?:[^"]|\\")*)" "(?P<selector_string>[^"]*)"$/ 1549 * @param string $element Element we look for 1550 * @param string $selectortype The type of what we look for 1551 * @throws DriverException 1552 * @throws ExpectationException 1553 */ 1554 public function i_post_tab_key_in_element($element, $selectortype) { 1555 if (!$this->running_javascript()) { 1556 throw new DriverException('Tab press step is not available with Javascript disabled'); 1557 } 1558 // Gets the node based on the requested selector type and locator. 1559 $node = $this->get_selected_node($selectortype, $element); 1560 $this->getSession()->getDriver()->post_key("\xEE\x80\x84", $node->getXpath()); 1561 } 1562 1563 /** 1564 * Checks if database family used is using one of the specified, else skip. (mysql, postgres, mssql, oracle, etc.) 1565 * 1566 * @Given /^database family used is one of the following:$/ 1567 * @param TableNode $databasefamilies list of database. 1568 * @return void. 1569 * @throws \Moodle\BehatExtension\Exception\SkippedException 1570 */ 1571 public function database_family_used_is_one_of_the_following(TableNode $databasefamilies) { 1572 global $DB; 1573 1574 $dbfamily = $DB->get_dbfamily(); 1575 1576 // Check if used db family is one of the specified ones. If yes then return. 1577 foreach ($databasefamilies->getRows() as $dbfamilytocheck) { 1578 if ($dbfamilytocheck[0] == $dbfamily) { 1579 return; 1580 } 1581 } 1582 1583 throw new \Moodle\BehatExtension\Exception\SkippedException(); 1584 } 1585 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |