[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 * Base class of all steps definitions. 19 * 20 * This script is only called from Behat as part of it's integration 21 * in Moodle. 22 * 23 * @package core 24 * @category test 25 * @copyright 2012 David Monllaó 26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 27 */ 28 29 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. 30 31 use Behat\Mink\Exception\ExpectationException as ExpectationException, 32 Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException, 33 Behat\Mink\Element\NodeElement as NodeElement; 34 35 /** 36 * Steps definitions base class. 37 * 38 * To extend by the steps definitions of the different Moodle components. 39 * 40 * It can not contain steps definitions to avoid duplicates, only utility 41 * methods shared between steps. 42 * 43 * @method NodeElement find_field(string $locator) Finds a form element 44 * @method NodeElement find_button(string $locator) Finds a form input submit element or a button 45 * @method NodeElement find_link(string $locator) Finds a link on a page 46 * @method NodeElement find_file(string $locator) Finds a forum input file element 47 * 48 * @package core 49 * @category test 50 * @copyright 2012 David Monllaó 51 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 52 */ 53 class behat_base extends Behat\MinkExtension\Context\RawMinkContext { 54 55 /** 56 * Small timeout. 57 * 58 * A reduced timeout for cases where self::TIMEOUT is too much 59 * and a simple $this->getSession()->getPage()->find() could not 60 * be enough. 61 */ 62 const REDUCED_TIMEOUT = 2; 63 64 /** 65 * The timeout for each Behat step (load page, wait for an element to load...). 66 */ 67 const TIMEOUT = 6; 68 69 /** 70 * And extended timeout for specific cases. 71 */ 72 const EXTENDED_TIMEOUT = 10; 73 74 /** 75 * The JS code to check that the page is ready. 76 */ 77 const PAGE_READY_JS = '(M && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")'; 78 79 /** 80 * Locates url, based on provided path. 81 * Override to provide custom routing mechanism. 82 * 83 * @see Behat\MinkExtension\Context\MinkContext 84 * @param string $path 85 * @return string 86 */ 87 protected function locate_path($path) { 88 $starturl = rtrim($this->getMinkParameter('base_url'), '/') . '/'; 89 return 0 !== strpos($path, 'http') ? $starturl . ltrim($path, '/') : $path; 90 } 91 92 /** 93 * Returns the first matching element. 94 * 95 * @link http://mink.behat.org/#traverse-the-page-selectors 96 * @param string $selector The selector type (css, xpath, named...) 97 * @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator... 98 * @param Exception $exception Otherwise we throw exception with generic info 99 * @param NodeElement $node Spins around certain DOM node instead of the whole page 100 * @param int $timeout Forces a specific time out (in seconds). 101 * @return NodeElement 102 */ 103 protected function find($selector, $locator, $exception = false, $node = false, $timeout = false) { 104 105 // Returns the first match. 106 $items = $this->find_all($selector, $locator, $exception, $node, $timeout); 107 return count($items) ? reset($items) : null; 108 } 109 110 /** 111 * Returns all matching elements. 112 * 113 * Adapter to Behat\Mink\Element\Element::findAll() using the spin() method. 114 * 115 * @link http://mink.behat.org/#traverse-the-page-selectors 116 * @param string $selector The selector type (css, xpath, named...) 117 * @param mixed $locator It depends on the $selector, can be the xpath, a name, a css locator... 118 * @param Exception $exception Otherwise we throw expcetion with generic info 119 * @param NodeElement $node Spins around certain DOM node instead of the whole page 120 * @param int $timeout Forces a specific time out (in seconds). If 0 is provided the default timeout will be applied. 121 * @return array NodeElements list 122 */ 123 protected function find_all($selector, $locator, $exception = false, $node = false, $timeout = false) { 124 125 // Generic info. 126 if (!$exception) { 127 128 // With named selectors we can be more specific. 129 if ($selector == 'named') { 130 $exceptiontype = $locator[0]; 131 $exceptionlocator = $locator[1]; 132 133 // If we are in a @javascript session all contents would be displayed as HTML characters. 134 if ($this->running_javascript()) { 135 $locator[1] = html_entity_decode($locator[1], ENT_NOQUOTES); 136 } 137 138 } else { 139 $exceptiontype = $selector; 140 $exceptionlocator = $locator; 141 } 142 143 $exception = new ElementNotFoundException($this->getSession(), $exceptiontype, null, $exceptionlocator); 144 } 145 146 $params = array('selector' => $selector, 'locator' => $locator); 147 // Pushing $node if required. 148 if ($node) { 149 $params['node'] = $node; 150 } 151 152 // How much we will be waiting for the element to appear. 153 if (!$timeout) { 154 $timeout = self::TIMEOUT; 155 $microsleep = false; 156 } else { 157 // Spinning each 0.1 seconds if the timeout was forced as we understand 158 // that is a special case and is good to refine the performance as much 159 // as possible. 160 $microsleep = true; 161 } 162 163 // Waits for the node to appear if it exists, otherwise will timeout and throw the provided exception. 164 return $this->spin( 165 function($context, $args) { 166 167 // If no DOM node provided look in all the page. 168 if (empty($args['node'])) { 169 return $context->getSession()->getPage()->findAll($args['selector'], $args['locator']); 170 } 171 172 // For nodes contained in other nodes we can not use the basic named selectors 173 // as they include unions and they would look for matches in the DOM root. 174 $elementxpath = $context->getSession()->getSelectorsHandler()->selectorToXpath($args['selector'], $args['locator']); 175 176 // Split the xpath in unions and prefix them with the container xpath. 177 $unions = explode('|', $elementxpath); 178 foreach ($unions as $key => $union) { 179 $union = trim($union); 180 181 // We are in the container node. 182 if (strpos($union, '.') === 0) { 183 $union = substr($union, 1); 184 } else if (strpos($union, '/') !== 0) { 185 // Adding the path separator in case it is not there. 186 $union = '/' . $union; 187 } 188 $unions[$key] = $args['node']->getXpath() . $union; 189 } 190 191 // We can not use usual Element::find() as it prefixes with DOM root. 192 return $context->getSession()->getDriver()->find(implode('|', $unions)); 193 }, 194 $params, 195 $timeout, 196 $exception, 197 $microsleep 198 ); 199 } 200 201 /** 202 * Finds DOM nodes in the page using named selectors. 203 * 204 * The point of using this method instead of Mink ones is the spin 205 * method of behat_base::find() that looks for the element until it 206 * is available or it timeouts, this avoids the false failures received 207 * when selenium tries to execute commands on elements that are not 208 * ready to be used. 209 * 210 * All steps that requires elements to be available before interact with 211 * them should use one of the find* methods. 212 * 213 * The methods calls requires a {'find_' . $elementtype}($locator) 214 * format, like find_link($locator), find_select($locator), 215 * find_button($locator)... 216 * 217 * @link http://mink.behat.org/#named-selectors 218 * @throws coding_exception 219 * @param string $name The name of the called method 220 * @param mixed $arguments 221 * @return NodeElement 222 */ 223 public function __call($name, $arguments) { 224 225 if (substr($name, 0, 5) !== 'find_') { 226 throw new coding_exception('The "' . $name . '" method does not exist'); 227 } 228 229 // Only the named selector identifier. 230 $cleanname = substr($name, 5); 231 232 // All named selectors shares the interface. 233 if (count($arguments) !== 1) { 234 throw new coding_exception('The "' . $cleanname . '" named selector needs the locator as it\'s single argument'); 235 } 236 237 // Redirecting execution to the find method with the specified selector. 238 // It will detect if it's pointing to an unexisting named selector. 239 return $this->find('named', 240 array( 241 $cleanname, 242 $this->getSession()->getSelectorsHandler()->xpathLiteral($arguments[0]) 243 ) 244 ); 245 } 246 247 /** 248 * Escapes the double quote character. 249 * 250 * Double quote is the argument delimiter, it can be escaped 251 * with a backslash, but we auto-remove this backslashes 252 * before the step execution, this method is useful when using 253 * arguments as arguments for other steps. 254 * 255 * @param string $string 256 * @return string 257 */ 258 public function escape($string) { 259 return str_replace('"', '\"', $string); 260 } 261 262 /** 263 * Executes the passed closure until returns true or time outs. 264 * 265 * In most cases the document.readyState === 'complete' will be enough, but sometimes JS 266 * requires more time to be completely loaded or an element to be visible or whatever is required to 267 * perform some action on an element; this method receives a closure which should contain the 268 * required statements to ensure the step definition actions and assertions have all their needs 269 * satisfied and executes it until they are satisfied or it timeouts. Redirects the return of the 270 * closure to the caller. 271 * 272 * The closures requirements to work well with this spin method are: 273 * - Must return false, null or '' if something goes wrong 274 * - Must return something != false if finishes as expected, this will be the (mixed) value 275 * returned by spin() 276 * 277 * The arguments of the closure are mixed, use $args depending on your needs. 278 * 279 * You can provide an exception to give more accurate feedback to tests writers, otherwise the 280 * closure exception will be used, but you must provide an exception if the closure does not throw 281 * an exception. 282 * 283 * @throws Exception If it timeouts without receiving something != false from the closure 284 * @param Function|array|string $lambda The function to execute or an array passed to call_user_func (maps to a class method) 285 * @param mixed $args Arguments to pass to the closure 286 * @param int $timeout Timeout in seconds 287 * @param Exception $exception The exception to throw in case it time outs. 288 * @param bool $microsleep If set to true it'll sleep micro seconds rather than seconds. 289 * @return mixed The value returned by the closure 290 */ 291 protected function spin($lambda, $args = false, $timeout = false, $exception = false, $microsleep = false) { 292 293 // Using default timeout which is pretty high. 294 if (!$timeout) { 295 $timeout = self::TIMEOUT; 296 } 297 if ($microsleep) { 298 // Will sleep 1/10th of a second by default for self::TIMEOUT seconds. 299 $loops = $timeout * 10; 300 } else { 301 // Will sleep for self::TIMEOUT seconds. 302 $loops = $timeout; 303 } 304 305 for ($i = 0; $i < $loops; $i++) { 306 // We catch the exception thrown by the step definition to execute it again. 307 try { 308 // We don't check with !== because most of the time closures will return 309 // direct Behat methods returns and we are not sure it will be always (bool)false 310 // if it just runs the behat method without returning anything $return == null. 311 if ($return = call_user_func($lambda, $this, $args)) { 312 return $return; 313 } 314 } catch (Exception $e) { 315 // We would use the first closure exception if no exception has been provided. 316 if (!$exception) { 317 $exception = $e; 318 } 319 // We wait until no exception is thrown or timeout expires. 320 continue; 321 } 322 323 if ($microsleep) { 324 usleep(100000); 325 } else { 326 sleep(1); 327 } 328 } 329 330 // Using coding_exception as is a development issue if no exception has been provided. 331 if (!$exception) { 332 $exception = new coding_exception('spin method requires an exception if the callback does not throw an exception'); 333 } 334 335 // Throwing exception to the user. 336 throw $exception; 337 } 338 339 /** 340 * Gets a NodeElement based on the locator and selector type received as argument from steps definitions. 341 * 342 * Use behat_base::get_text_selector_node() for text-based selectors. 343 * 344 * @throws ElementNotFoundException Thrown by behat_base::find 345 * @param string $selectortype 346 * @param string $element 347 * @return NodeElement 348 */ 349 protected function get_selected_node($selectortype, $element) { 350 351 // Getting Mink selector and locator. 352 list($selector, $locator) = $this->transform_selector($selectortype, $element); 353 354 // Returns the NodeElement. 355 return $this->find($selector, $locator); 356 } 357 358 /** 359 * Gets a NodeElement based on the locator and selector type received as argument from steps definitions. 360 * 361 * @throws ElementNotFoundException Thrown by behat_base::find 362 * @param string $selectortype 363 * @param string $element 364 * @return NodeElement 365 */ 366 protected function get_text_selector_node($selectortype, $element) { 367 368 // Getting Mink selector and locator. 369 list($selector, $locator) = $this->transform_text_selector($selectortype, $element); 370 371 // Returns the NodeElement. 372 return $this->find($selector, $locator); 373 } 374 375 /** 376 * Gets the requested element inside the specified container. 377 * 378 * @throws ElementNotFoundException Thrown by behat_base::find 379 * @param mixed $selectortype The element selector type. 380 * @param mixed $element The element locator. 381 * @param mixed $containerselectortype The container selector type. 382 * @param mixed $containerelement The container locator. 383 * @return NodeElement 384 */ 385 protected function get_node_in_container($selectortype, $element, $containerselectortype, $containerelement) { 386 387 // Gets the container, it will always be text based. 388 $containernode = $this->get_text_selector_node($containerselectortype, $containerelement); 389 390 list($selector, $locator) = $this->transform_selector($selectortype, $element); 391 392 // Specific exception giving info about where can't we find the element. 393 $locatorexceptionmsg = $element . '" in the "' . $containerelement. '" "' . $containerselectortype. '"'; 394 $exception = new ElementNotFoundException($this->getSession(), $selectortype, null, $locatorexceptionmsg); 395 396 // Looks for the requested node inside the container node. 397 return $this->find($selector, $locator, $exception, $containernode); 398 } 399 400 /** 401 * Transforms from step definition's argument style to Mink format. 402 * 403 * Mink has 3 different selectors css, xpath and named, where named 404 * selectors includes link, button, field... to simplify and group multiple 405 * steps in one we use the same interface, considering all link, buttons... 406 * at the same level as css selectors and xpath; this method makes the 407 * conversion from the arguments received by the steps to the selectors and locators 408 * required to interact with Mink. 409 * 410 * @throws ExpectationException 411 * @param string $selectortype It can be css, xpath or any of the named selectors. 412 * @param string $element The locator (or string) we are looking for. 413 * @return array Contains the selector and the locator expected by Mink. 414 */ 415 protected function transform_selector($selectortype, $element) { 416 417 // Here we don't know if an allowed text selector is being used. 418 $selectors = behat_selectors::get_allowed_selectors(); 419 if (!isset($selectors[$selectortype])) { 420 throw new ExpectationException('The "' . $selectortype . '" selector type does not exist', $this->getSession()); 421 } 422 423 return behat_selectors::get_behat_selector($selectortype, $element, $this->getSession()); 424 } 425 426 /** 427 * Transforms from step definition's argument style to Mink format. 428 * 429 * Delegates all the process to behat_base::transform_selector() checking 430 * the provided $selectortype. 431 * 432 * @throws ExpectationException 433 * @param string $selectortype It can be css, xpath or any of the named selectors. 434 * @param string $element The locator (or string) we are looking for. 435 * @return array Contains the selector and the locator expected by Mink. 436 */ 437 protected function transform_text_selector($selectortype, $element) { 438 439 $selectors = behat_selectors::get_allowed_text_selectors(); 440 if (empty($selectors[$selectortype])) { 441 throw new ExpectationException('The "' . $selectortype . '" selector can not be used to select text nodes', $this->getSession()); 442 } 443 444 return $this->transform_selector($selectortype, $element); 445 } 446 447 /** 448 * Returns whether the scenario is running in a browser that can run Javascript or not. 449 * 450 * @return boolean 451 */ 452 protected function running_javascript() { 453 return get_class($this->getSession()->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver'; 454 } 455 456 /** 457 * Spins around an element until it exists 458 * 459 * @throws ExpectationException 460 * @param string $element 461 * @param string $selectortype 462 * @return void 463 */ 464 protected function ensure_element_exists($element, $selectortype) { 465 466 // Getting the behat selector & locator. 467 list($selector, $locator) = $this->transform_selector($selectortype, $element); 468 469 // Exception if it timesout and the element is still there. 470 $msg = 'The "' . $element . '" element does not exist and should exist'; 471 $exception = new ExpectationException($msg, $this->getSession()); 472 473 // It will stop spinning once the find() method returns true. 474 $this->spin( 475 function($context, $args) { 476 // We don't use behat_base::find as it is already spinning. 477 if ($context->getSession()->getPage()->find($args['selector'], $args['locator'])) { 478 return true; 479 } 480 return false; 481 }, 482 array('selector' => $selector, 'locator' => $locator), 483 self::EXTENDED_TIMEOUT, 484 $exception, 485 true 486 ); 487 488 } 489 490 /** 491 * Spins until the element does not exist 492 * 493 * @throws ExpectationException 494 * @param string $element 495 * @param string $selectortype 496 * @return void 497 */ 498 protected function ensure_element_does_not_exist($element, $selectortype) { 499 500 // Getting the behat selector & locator. 501 list($selector, $locator) = $this->transform_selector($selectortype, $element); 502 503 // Exception if it timesout and the element is still there. 504 $msg = 'The "' . $element . '" element exists and should not exist'; 505 $exception = new ExpectationException($msg, $this->getSession()); 506 507 // It will stop spinning once the find() method returns false. 508 $this->spin( 509 function($context, $args) { 510 // We don't use behat_base::find() as we are already spinning. 511 if (!$context->getSession()->getPage()->find($args['selector'], $args['locator'])) { 512 return true; 513 } 514 return false; 515 }, 516 array('selector' => $selector, 'locator' => $locator), 517 self::EXTENDED_TIMEOUT, 518 $exception, 519 true 520 ); 521 } 522 523 /** 524 * Ensures that the provided node is visible and we can interact with it. 525 * 526 * @throws ExpectationException 527 * @param NodeElement $node 528 * @return void Throws an exception if it times out without the element being visible 529 */ 530 protected function ensure_node_is_visible($node) { 531 532 if (!$this->running_javascript()) { 533 return; 534 } 535 536 // Exception if it timesout and the element is still there. 537 $msg = 'The "' . $node->getXPath() . '" xpath node is not visible and it should be visible'; 538 $exception = new ExpectationException($msg, $this->getSession()); 539 540 // It will stop spinning once the isVisible() method returns true. 541 $this->spin( 542 function($context, $args) { 543 if ($args->isVisible()) { 544 return true; 545 } 546 return false; 547 }, 548 $node, 549 self::EXTENDED_TIMEOUT, 550 $exception, 551 true 552 ); 553 } 554 555 /** 556 * Ensures that the provided element is visible and we can interact with it. 557 * 558 * Returns the node in case other actions are interested in using it. 559 * 560 * @throws ExpectationException 561 * @param string $element 562 * @param string $selectortype 563 * @return NodeElement Throws an exception if it times out without being visible 564 */ 565 protected function ensure_element_is_visible($element, $selectortype) { 566 567 if (!$this->running_javascript()) { 568 return; 569 } 570 571 $node = $this->get_selected_node($selectortype, $element); 572 $this->ensure_node_is_visible($node); 573 574 return $node; 575 } 576 577 /** 578 * Ensures that all the page's editors are loaded. 579 * 580 * @deprecated since Moodle 2.7 MDL-44084 - please do not use this function any more. 581 * @throws ElementNotFoundException 582 * @throws ExpectationException 583 * @return void 584 */ 585 protected function ensure_editors_are_loaded() { 586 global $CFG; 587 588 if (empty($CFG->behat_usedeprecated)) { 589 debugging('Function behat_base::ensure_editors_are_loaded() is deprecated. It is no longer required.'); 590 } 591 return; 592 } 593 594 /** 595 * Change browser window size. 596 * - small: 640x480 597 * - medium: 1024x768 598 * - large: 2560x1600 599 * 600 * @param string $windowsize size of window. 601 * @throws ExpectationException 602 */ 603 protected function resize_window($windowsize) { 604 // Non JS don't support resize window. 605 if (!$this->running_javascript()) { 606 return; 607 } 608 609 switch ($windowsize) { 610 case "small": 611 $width = 640; 612 $height = 480; 613 break; 614 case "medium": 615 $width = 1024; 616 $height = 768; 617 break; 618 case "large": 619 $width = 2560; 620 $height = 1600; 621 break; 622 default: 623 preg_match('/^(\d+x\d+)$/', $windowsize, $matches); 624 if (empty($matches) || (count($matches) != 2)) { 625 throw new ExpectationException("Invalid screen size, can't resize", $this->getSession()); 626 } 627 $size = explode('x', $windowsize); 628 $width = (int) $size[0]; 629 $height = (int) $size[1]; 630 } 631 $this->getSession()->getDriver()->resizeWindow($width, $height); 632 } 633 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |