[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/behat/form_field/ -> behat_form_select.php (source)

   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   * Single select form field class.
  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  require_once (__DIR__  . '/behat_form_field.php');
  29  
  30  /**
  31   * Single select form field.
  32   *
  33   * @package    core_form
  34   * @category   test
  35   * @copyright  2012 David Monllaó
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  class behat_form_select extends behat_form_field {
  39  
  40      /**
  41       * Sets the value(s) of a select element.
  42       *
  43       * Seems an easy select, but there are lots of combinations
  44       * of browsers and operative systems and each one manages the
  45       * autosubmits and the multiple option selects in a different way.
  46       *
  47       * @param string $value plain value or comma separated values if multiple. Commas in values escaped with backslash.
  48       * @return void
  49       */
  50      public function set_value($value) {
  51  
  52          // In some browsers we select an option and it triggers all the
  53          // autosubmits and works as expected but not in all of them, so we
  54          // try to catch all the possibilities to make this function work as
  55          // expected.
  56  
  57          // Get the internal id of the element we are going to click.
  58          // This kind of internal IDs are only available in the selenium wire
  59          // protocol, so only available using selenium drivers, phantomjs and family.
  60          if ($this->running_javascript()) {
  61              $currentelementid = $this->get_internal_field_id();
  62          }
  63  
  64          // Is the select multiple?
  65          $multiple = $this->field->hasAttribute('multiple');
  66  
  67          // By default, assume the passed value is a non-multiple option.
  68          $options = array(trim($value));
  69  
  70          // Here we select the option(s).
  71          if ($multiple) {
  72              // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash.
  73              $options = preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $value));
  74              // This is a multiple select, let's pass the multiple flag after first option.
  75              $afterfirstoption = false;
  76              foreach ($options as $option) {
  77                  $this->field->selectOption(trim($option), $afterfirstoption);
  78                  $afterfirstoption = true;
  79              }
  80          } else {
  81              // This is a single select, let's pass the last one specified.
  82              $this->field->selectOption(end($options));
  83          }
  84  
  85          // With JS disabled this is enough and we finish here.
  86          if (!$this->running_javascript()) {
  87              return;
  88          }
  89  
  90          // With JS enabled we add more clicks as some selenium
  91          // drivers requires it to fire JS events.
  92  
  93          // In some browsers the selectOption actions can perform a form submit or reload page
  94          // so we need to ensure the element is still available to continue interacting
  95          // with it. We don't wait here.
  96          // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch.
  97          $selectxpath = $this->field->getXpath();
  98          if (!$this->session->getDriver()->find($selectxpath)) {
  99              return;
 100          }
 101  
 102          // We also check the selenium internal element id, if it have changed
 103          // we are dealing with an autosubmit that was already executed, and we don't to
 104          // execute anything else as the action we wanted was already performed.
 105          if ($currentelementid != $this->get_internal_field_id()) {
 106              return;
 107          }
 108  
 109          // Wait for all the possible AJAX requests that have been
 110          // already triggered by selectOption() to be finished.
 111          $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
 112  
 113          // Wrapped in try & catch as the element may disappear if an AJAX request was submitted.
 114          try {
 115              $multiple = $this->field->hasAttribute('multiple');
 116          } catch (Exception $e) {
 117              // We do not specify any specific Exception type as there are
 118              // different exceptions that can be thrown by the driver and
 119              // we can not control them all, also depending on the selenium
 120              // version the exception type can change.
 121              return;
 122          }
 123  
 124          // Single select sometimes needs an extra click in the option.
 125          if (!$multiple) {
 126  
 127              // Var $options only contains 1 option.
 128              $optionxpath = $this->get_option_xpath(end($options), $selectxpath);
 129  
 130              // Using the driver direcly because Element methods are messy when dealing
 131              // with elements inside containers.
 132              if ($optionnodes = $this->session->getDriver()->find($optionxpath)) {
 133  
 134                  // Wrapped in a try & catch as we can fall into race conditions
 135                  // and the element may not be there.
 136                  try {
 137                      current($optionnodes)->click();
 138                  } catch (Exception $e) {
 139                      // We continue and return as this means that the element is not there or it is not the same.
 140                      return;
 141                  }
 142              }
 143  
 144          } else {
 145  
 146              // Wrapped in a try & catch as we can fall into race conditions
 147              // and the element may not be there.
 148              try {
 149                  // Multiple ones needs the click in the select.
 150                  $this->field->click();
 151              } catch (Exception $e) {
 152                  // We continue and return as this means that the element is not there or it is not the same.
 153                  return;
 154              }
 155  
 156              // We also check that the option(s) are still there. We neither wait.
 157              foreach ($options as $option) {
 158                  $optionxpath = $this->get_option_xpath($option, $selectxpath);
 159                  if (!$this->session->getDriver()->find($optionxpath)) {
 160                      return;
 161                  }
 162              }
 163  
 164              // Wait for all the possible AJAX requests that have been
 165              // already triggered by clicking on the field to be finished.
 166              $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
 167  
 168              // Wrapped in a try & catch as we can fall into race conditions
 169              // and the element may not be there.
 170              try {
 171  
 172                  // Repeating the select(s) as some drivers (chrome that I know) are moving
 173                  // to another option after the general select field click above.
 174                  $afterfirstoption = false;
 175                  foreach ($options as $option) {
 176                      $this->field->selectOption(trim($option), $afterfirstoption);
 177                      $afterfirstoption = true;
 178                  }
 179              } catch (Exception $e) {
 180                  // We continue and return as this means that the element is not there or it is not the same.
 181                  return;
 182              }
 183          }
 184      }
 185  
 186      /**
 187       * Returns the text of the currently selected options.
 188       *
 189       * @return string Comma separated if multiple options are selected. Commas in option texts escaped with backslash.
 190       */
 191      public function get_value() {
 192          return $this->get_selected_options();
 193      }
 194  
 195      /**
 196       * Returns whether the provided argument matches the current value.
 197       *
 198       * @param mixed $expectedvalue
 199       * @return bool
 200       */
 201      public function matches($expectedvalue) {
 202  
 203          $multiple = $this->field->hasAttribute('multiple');
 204  
 205          // Same implementation as the parent if it is a single select.
 206          if (!$multiple) {
 207              $cleanexpectedvalue = trim($expectedvalue);
 208              $selectedtext = trim($this->get_selected_options());
 209              $selectedvalue = trim($this->get_selected_options(false));
 210              if ($cleanexpectedvalue != $selectedvalue && $cleanexpectedvalue != $selectedtext) {
 211                  return false;
 212              }
 213              return true;
 214          }
 215  
 216          // We are dealing with a multi-select.
 217  
 218          // Can pass multiple comma separated, with valuable commas escaped with backslash.
 219          $expectedarr = array(); // Array of passed text options to test.
 220  
 221          // Unescape + trim all options and flip it to have the expected values as keys.
 222          $expectedoptions = $this->get_unescaped_options($expectedvalue);
 223  
 224          // Get currently selected option's texts.
 225          $texts = $this->get_selected_options(true);
 226          $selectedoptiontexts = $this->get_unescaped_options($texts);
 227  
 228          // Get currently selected option's values.
 229          $values = $this->get_selected_options(false);
 230          $selectedoptionvalues = $this->get_unescaped_options($values);
 231  
 232          // Precheck to speed things up.
 233          if (count($expectedoptions) !== count($selectedoptiontexts) ||
 234                  count($expectedoptions) !== count($selectedoptionvalues)) {
 235              return false;
 236          }
 237  
 238          // We check against string-ordered lists of options.
 239          if ($expectedoptions != $selectedoptiontexts &&
 240                  $expectedoptions != $selectedoptionvalues) {
 241              return false;
 242          }
 243  
 244          return true;
 245      }
 246  
 247      /**
 248       * Cleans the list of options and returns it as a string separating options with |||.
 249       *
 250       * @param string $value The string containing the escaped options.
 251       * @return string The options
 252       */
 253      protected function get_unescaped_options($value) {
 254  
 255          // Can be multiple comma separated, with valuable commas escaped with backslash.
 256          $optionsarray = array_map(
 257              'trim',
 258              preg_replace('/\\\,/', ',',
 259                  preg_split('/(?<!\\\),/', $value)
 260             )
 261          );
 262  
 263          // Sort by value (keeping the keys is irrelevant).
 264          core_collator::asort($optionsarray, SORT_STRING);
 265  
 266          // Returning it as a string which is easier to match against other values.
 267          return implode('|||', $optionsarray);
 268      }
 269  
 270      /**
 271       * Returns the field selected values.
 272       *
 273       * Externalized from the common behat_form_field API method get_value() as
 274       * matches() needs to check against both values and texts.
 275       *
 276       * @param bool $returntexts Returns the options texts or the options values.
 277       * @return string
 278       */
 279      protected function get_selected_options($returntexts = true) {
 280  
 281          $method = 'getHtml';
 282          if ($returntexts === false) {
 283              $method = 'getValue';
 284          }
 285  
 286          // Is the select multiple?
 287          $multiple = $this->field->hasAttribute('multiple');
 288  
 289          $selectedoptions = array(); // To accumulate found selected options.
 290  
 291          // Selenium getValue() implementation breaks - separates - values having
 292          // commas within them, so we'll be looking for options with the 'selected' attribute instead.
 293          if ($this->running_javascript()) {
 294              // Get all the options in the select and extract their value/text pairs.
 295              $alloptions = $this->field->findAll('xpath', '//option');
 296              foreach ($alloptions as $option) {
 297                  // Is it selected?
 298                  if ($option->hasAttribute('selected')) {
 299                      if ($multiple) {
 300                          // If the select is multiple, text commas must be encoded.
 301                          $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
 302                      } else {
 303                          $selectedoptions[] = trim($option->{$method}());
 304                      }
 305                  }
 306              }
 307  
 308          } else {
 309              // Goutte does not keep the 'selected' attribute updated, but its getValue() returns
 310              // the selected elements correctly, also those having commas within them.
 311  
 312              // Goutte returns the values as an array or as a string depending
 313              // on whether multiple options are selected or not.
 314              $values = $this->field->getValue();
 315              if (!is_array($values)) {
 316                  $values = array($values);
 317              }
 318  
 319              // Get all the options in the select and extract their value/text pairs.
 320              $alloptions = $this->field->findAll('xpath', '//option');
 321              foreach ($alloptions as $option) {
 322                  // Is it selected?
 323                  if (in_array($option->getValue(), $values)) {
 324                      if ($multiple) {
 325                          // If the select is multiple, text commas must be encoded.
 326                          $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
 327                      } else {
 328                          $selectedoptions[] = trim($option->{$method}());
 329                      }
 330                  }
 331              }
 332          }
 333  
 334          return implode(', ', $selectedoptions);
 335      }
 336  
 337      /**
 338       * Returns the opton XPath based on it's select xpath.
 339       *
 340       * @param string $option
 341       * @param string $selectxpath
 342       * @return string xpath
 343       */
 344      protected function get_option_xpath($option, $selectxpath) {
 345          $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option));
 346          return $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]";
 347      }
 348  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1