See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 403]
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 * Generic moodleforms field. 19 * 20 * @package core_form 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 use Behat\Mink\Element\NodeElement; 29 use Behat\Mink\Session; 30 31 /** 32 * Representation of a form field. 33 * 34 * Basically an interface with Mink session. 35 * 36 * @package core_form 37 * @category test 38 * @copyright 2012 David MonllaĆ³ 39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 40 */ 41 class behat_form_field implements behat_session_interface { 42 43 // All of the functionality of behat_base is shared with form fields via the behat_session_trait trait. 44 use behat_session_trait; 45 46 /** 47 * @var Session Behat session. 48 */ 49 protected $session; 50 51 /** 52 * @var NodeElement The field DOM node to interact with. 53 */ 54 protected $field; 55 56 /** 57 * @var string The field's locator. 58 */ 59 protected $fieldlocator = false; 60 61 /** 62 * Returns the Mink session. 63 * 64 * @param string|null $name name of the session OR active session will be used 65 * @return \Behat\Mink\Session 66 */ 67 public function getSession($name = null) { 68 return $this->session; 69 } 70 71 72 /** 73 * General constructor with the node and the session to interact with. 74 * 75 * @param Session $session Reference to Mink session to traverse/modify the page DOM. 76 * @param NodeElement $fieldnode The field DOM node 77 * @return void 78 */ 79 public function __construct(Session $session, NodeElement $fieldnode) { 80 $this->session = $session; 81 $this->field = $fieldnode; 82 } 83 84 /** 85 * Sets the value to a field. 86 * 87 * @param string $value 88 * @return void 89 */ 90 public function set_value($value) { 91 // We delegate to the best guess, if we arrived here 92 // using the generic behat_form_field is because we are 93 // dealing with a fgroup element. 94 $instance = $this->guess_type(); 95 return $instance->set_value($value); 96 } 97 98 /** 99 * Returns the current value of the select element. 100 * 101 * @return string 102 */ 103 public function get_value() { 104 // We delegate to the best guess, if we arrived here 105 // using the generic behat_form_field is because we are 106 // dealing with a fgroup element. 107 $instance = $this->guess_type(); 108 return $instance->get_value(); 109 } 110 111 /** 112 * Presses specific keyboard key. 113 * 114 * @param mixed $char could be either char ('b') or char-code (98) 115 * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta') 116 */ 117 public function key_press($char, $modifier = null) { 118 // We delegate to the best guess, if we arrived here 119 // using the generic behat_form_field is because we are 120 // dealing with a fgroup element. 121 $instance = $this->guess_type(); 122 $instance->field->keyDown($char, $modifier); 123 try { 124 $instance->field->keyPress($char, $modifier); 125 $instance->field->keyUp($char, $modifier); 126 } catch (\Facebook\WebDriver\Exception\WebDriverException $e) { 127 // If the JS handler attached to keydown or keypress destroys the element 128 // the later events may trigger errors because form element no longer exist 129 // or is not visible. Ignore such exceptions here. 130 } catch (\Behat\Mink\Exception\ElementNotFoundException $e) { 131 // Other Mink drivers can throw this for the same reason as above. 132 } 133 } 134 135 /** 136 * Generic match implementation 137 * 138 * Will work well with text-based fields, extension required 139 * for most of the other cases. 140 * 141 * @param string $expectedvalue 142 * @return bool The provided value matches the field value? 143 */ 144 public function matches($expectedvalue) { 145 // We delegate to the best guess, if we arrived here 146 // using the generic behat_form_field is because we are 147 // dealing with a fgroup element. 148 $instance = $this->guess_type(); 149 return $instance->matches($expectedvalue); 150 } 151 152 /** 153 * Get the value of an attribute set on this field. 154 * 155 * @param string $name The attribute name 156 * @return string The attribute value 157 */ 158 public function get_attribute($name) { 159 return $this->field->getAttribute($name); 160 } 161 162 /** 163 * Guesses the element type we are dealing with in case is not a text-based element. 164 * 165 * This class is the generic field type, behat_field_manager::get_form_field() 166 * should be able to find the appropiate class for the field type, but 167 * in cases like moodle form group elements we can not find the type of 168 * the field through the DOM so we also need to take care of the 169 * different field types from here. If we need to deal with more complex 170 * moodle form elements we will need to refactor this simple HTML elements 171 * guess method. 172 * 173 * @return behat_form_field 174 */ 175 private function guess_type() { 176 return $this->get_field_instance_for_element($this->field); 177 } 178 179 /** 180 * Returns the appropriate form field object for a given node element. 181 * 182 * @param NodeElement $element The node element 183 * @return behat_form_field 184 */ 185 protected function get_field_instance_for_element(NodeElement $element): behat_form_field { 186 global $CFG; 187 188 // We default to the text-based field if nothing was detected. 189 if (!$type = behat_field_manager::guess_field_type($element, $this->session)) { 190 $type = 'text'; 191 } 192 193 $classname = 'behat_form_' . $type; 194 $classpath = $CFG->dirroot . '/lib/behat/form_field/' . $classname . '.php'; 195 require_once($classpath); 196 197 return new $classname($this->session, $element); 198 } 199 200 /** 201 * Returns whether the scenario is running in a browser that can run Javascript or not. 202 * 203 * @return bool 204 */ 205 protected function running_javascript() { 206 return get_class($this->session->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver'; 207 } 208 209 /** 210 * Waits for all the JS activity to be completed. 211 * 212 * @return bool Whether any JS is still pending completion. 213 */ 214 protected function wait_for_pending_js() { 215 if (!$this->running_javascript()) { 216 // JS is not available therefore there is nothing to wait for. 217 return false; 218 } 219 220 return behat_base::wait_for_pending_js_in_session($this->session); 221 } 222 223 /** 224 * Gets the field internal id used by selenium wire protocol. 225 * 226 * Only available when running_javascript(). 227 * 228 * @throws coding_exception 229 * @return int 230 */ 231 protected function get_internal_field_id() { 232 if (!$this->running_javascript()) { 233 throw new coding_exception('You can only get an internal ID using the selenium driver.'); 234 } 235 236 return $this->getSession() 237 ->getDriver() 238 ->getWebDriver() 239 ->findElement(WebDriverBy::xpath($node->getXpath())) 240 ->getID(); 241 } 242 243 /** 244 * Checks if the provided text matches the field value. 245 * 246 * @param string $expectedvalue 247 * @param string|null $actualvalue The actual value. If not specified, this will be fetched from $this->get_value(). 248 * @return bool 249 */ 250 protected function text_matches($expectedvalue, ?string $actualvalue = null): bool { 251 $actualvalue = $actualvalue ?? $this->get_value(); 252 253 // Non strict string comparison. 254 if (trim($expectedvalue) == trim($actualvalue)) { 255 return true; 256 } 257 258 // Do one more matching attempt for floats that are valid with current decsep in use 259 // (let's continue non strict comparing them as strings, but once unformatted). 260 $expectedfloat = unformat_float(trim($expectedvalue), true); 261 $actualfloat = unformat_float(trim($actualvalue), true); 262 // If they aren't null or false, then we are good to be compared (basically is_numeric()). 263 $goodfloats = !is_null($expectedfloat) && ($expectedfloat !== false) && 264 !is_null($actualfloat) && ($actualfloat !== false); 265 if ($goodfloats && ((string)$expectedfloat == (string)$actualfloat)) { 266 return true; 267 } 268 269 return false; 270 } 271 272 /** 273 * Gets the field locator. 274 * 275 * Defaults to the field label but you can 276 * specify other locators if you are interested. 277 * 278 * Public visibility as in most cases will be hard to 279 * use this method in a generic way, as fields can 280 * be selected using multiple ways (label, id, name...). 281 * 282 * @throws coding_exception 283 * @param string $locatortype 284 * @return string 285 */ 286 protected function get_field_locator($locatortype = false) { 287 288 if (!empty($this->fieldlocator)) { 289 return $this->fieldlocator; 290 } 291 292 $fieldid = $this->field->getAttribute('id'); 293 294 // Defaults to label. 295 if ($locatortype == 'label' || $locatortype == false) { 296 297 $labelnode = $this->session->getPage()->find('xpath', "//label[@for='$fieldid']|//p[@id='{$fieldid}_label']"); 298 299 // Exception only if $locatortype was specified. 300 if (!$labelnode && $locatortype == 'label') { 301 throw new coding_exception('Field with "' . $fieldid . '" id does not have a label.'); 302 } 303 304 $this->fieldlocator = $labelnode->getText(); 305 } 306 307 // Let's look for the name as a second option (more popular than 308 // id's when pointing to fields). 309 if (($locatortype == 'name' || $locatortype == false) && 310 empty($this->fieldlocator)) { 311 312 $name = $this->field->getAttribute('name'); 313 314 // Exception only if $locatortype was specified. 315 if (!$name && $locatortype == 'name') { 316 throw new coding_exception('Field with "' . $fieldid . '" id does not have a name attribute.'); 317 } 318 319 $this->fieldlocator = $name; 320 } 321 322 // Otherwise returns the id if no specific locator type was provided. 323 if (empty($this->fieldlocator)) { 324 $this->fieldlocator = $fieldid; 325 } 326 327 return $this->fieldlocator; 328 } 329 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body