*/
public function set_value($value) {
- if (!$this->running_javascript()) {
- $this->field->check();
- return;
- }
-
if (!empty($value) && !$this->field->isChecked()) {
+
+ if (!$this->running_javascript()) {
+ $this->field->check();
+ return;
+ }
+
// Check it if it should be checked and it is not.
$this->field->click();
+ // Trigger the onchange event as triggered when 'checking' the checkbox.
+ $this->session->getDriver()->triggerSynScript(
+ $this->field->getXPath(),
+ "Syn.trigger('change', {}, {{ELEMENT}})"
+ );
+
} else if (empty($value) && $this->field->isChecked()) {
+
+ if (!$this->running_javascript()) {
+ $this->field->uncheck();
+ return;
+ }
+
// Uncheck if it is checked and shouldn't.
$this->field->click();
+
+ // Trigger the onchange event as triggered when 'checking' the checkbox.
+ $this->session->getDriver()->triggerSynScript(
+ $this->field->getXPath(),
+ "Syn.trigger('change', {}, {{ELEMENT}})"
+ );
}
}
public function get_value() {
return $this->field->isChecked();
}
+
+ /**
+ * Is it enabled?
+ *
+ * @param string $expectedvalue Anything !empty() is considered checked.
+ * @return bool
+ */
+ public function matches($expectedvalue = false) {
+
+ $ischecked = $this->field->isChecked();
+
+ // Any non-empty value provided means that it should be checked.
+ if (!empty($expectedvalue) && $ischecked) {
+ return true;
+ } else if (empty($expectedvalue) && !$ischecked) {
+ return true;
+ }
+
+ return false;
+ }
+
}
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
-require_once(__DIR__ . '/behat_form_select.php');
+require_once(__DIR__ . '/behat_form_group.php');
/**
* Date form field.
*
- * Simple extension of behat_form_select to allow date-type
- * select fields to be filled like select elements instead of
- * text elements.
+ * Simple extension of behat_form_group to allow the different
+ * date_selector fields to be filled according to it's type.
*
* This class will be refactored in case we are interested in
* creating more complex formats to fill date and date-time fields.
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class behat_form_date_selector extends behat_form_select {}
+class behat_form_date_selector extends behat_form_group {}
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
-require_once(__DIR__ . '/behat_form_select.php');
+require_once(__DIR__ . '/behat_form_date_selector.php');
/**
* Date time form field.
*
- * Simple extension of behat_form_select to allow datetime-type
- * select fields to be filled like select elements instead of
- * text elements.
- *
* This class will be refactored in case we are interested in
- * creating more complex formats to fill date and date-time fields.
+ * creating more complex formats to fill date-time fields.
*
* @package core_form
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class behat_form_date_time_selector extends behat_form_select {}
+class behat_form_date_time_selector extends behat_form_date_selector {}
}
}
+ /**
+ * Generic match implementation
+ *
+ * Will work well with text-based fields, extension required
+ * for most of the other cases.
+ *
+ * @param string $expectedvalue
+ * @return bool The provided value matches the field value?
+ */
+ public function matches($expectedvalue) {
+
+ // If we are not dealing with a text-based tag try to find the most appropiate
+ // behat_form_* class to deal with it.
+ if ($instance = $this->guess_type()) {
+ return $instance->matches($expectedvalue);
+ }
+
+ // Text-based comparison.
+ if (trim($expectedvalue) != trim($this->get_value())) {
+ return false;
+ }
+ return true;
+ }
+
/**
* Guesses the element type we are dealing with in case is not a text-based element.
*
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Generic group field class.
+ *
+ * @package core_form
+ * @category test
+ * @copyright 2014 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/behat_form_field.php');
+
+/**
+ * Class to re-guess the field type as grouped fields can have different field types.
+ *
+ * When filling fields in a fgroup field element we don't know what kind
+ * of field are we dealing with, so we should re-guess it.
+ *
+ * @package core_form
+ * @category test
+ * @copyright 2014 David Monllaó
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_form_group extends behat_form_field {}
* Radio input form field.
*
* Extends behat_form_checkbox as the set_value() behaviour
- * is the same.
+ * is the same and it behaves closer to a checkbox than to
+ * a text field.
*
* This form field type can be added to forms as any other
* moodle form element, but it does not make sense without
/**
* Returns the radio input value attribute.
*
+ * Here we can not extend behat_form_checkbox because
+ * isChecked() does internally a (bool)getValue() and
+ * it is not good for radio buttons.
+ *
* @return string The value attribute
*/
public function get_value() {
- return $this->field->getValue();
+ return (bool)$this->field->getAttribute('checked');
+ }
+
+ /**
+ * Sets the value of a radio
+ *
+ * Partially overwriting behat_form_checkbox
+ * implementation as when JS is disabled we
+ * can not check() and we should use setValue()
+ *
+ * @param string $value
+ * @return void
+ */
+ public function set_value($value) {
+
+ if ($this->running_javascript()) {
+ parent::set_value($value);
+ } else {
+ // Goutte does not accept a check nor a click in an input[type=radio].
+ $this->field->setValue($this->field->getAttribute('value'));
+ }
+ }
+
+ /**
+ * Returns whether the provided value matches the current value or not.
+ *
+ * @param string $expectedvalue
+ * @return bool
+ */
+ public function matches($expectedvalue = false) {
+ if (trim($expectedvalue) != trim($this->get_value())) {
+ return false;
+ }
+ return true;
}
}
// In some browsers the selectOption actions can perform a form submit or reload page
// so we need to ensure the element is still available to continue interacting
// with it. We don't wait here.
+ // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch.
$selectxpath = $this->field->getXpath();
if (!$this->session->getDriver()->find($selectxpath)) {
return;
return;
}
- // We also check that the option(s) are still there. We neither wait.
- foreach ($options as $option) {
- $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option));
- $optionxpath = $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]";
- if (!$this->session->getDriver()->find($optionxpath)) {
- return;
- }
- }
+ // Wait for all the possible AJAX requests that have been
+ // already triggered by selectOption() to be finished.
+ $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
// Wrapped in try & catch as the element may disappear if an AJAX request was submitted.
try {
return;
}
- // Wait for all the possible AJAX requests that have been
- // already triggered by selectOption() to be finished.
- $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
// Single select sometimes needs an extra click in the option.
if (!$multiple) {
+ // $options only contains 1 option.
+ $optionxpath = $this->get_option_xpath(end($options), $selectxpath);
+
// Using the driver direcly because Element methods are messy when dealing
// with elements inside containers.
- $optionnodes = $this->session->getDriver()->find($optionxpath);
- if ($optionnodes) {
+ if ($optionnodes = $this->session->getDriver()->find($optionxpath)) {
+
// Wrapped in a try & catch as we can fall into race conditions
// and the element may not be there.
try {
return;
}
- // We ensure that the option is still there.
- if (!$this->session->getDriver()->find($optionxpath)) {
- return;
+ // We also check that the option(s) are still there. We neither wait.
+ foreach ($options as $option) {
+ $optionxpath = $this->get_option_xpath($option, $selectxpath);
+ if (!$this->session->getDriver()->find($optionxpath)) {
+ return;
+ }
}
// Wait for all the possible AJAX requests that have been
- // already triggered by selectOption() to be finished.
+ // already triggered by clicking on the field to be finished.
$this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
// Wrapped in a try & catch as we can fall into race conditions
// and the element may not be there.
try {
+
// Repeating the select(s) as some drivers (chrome that I know) are moving
// to another option after the general select field click above.
+ $afterfirstoption = false;
foreach ($options as $option) {
- $this->field->selectOption(trim($option), true);
+ $this->field->selectOption(trim($option), $afterfirstoption);
+ $afterfirstoption = true;
}
} catch (Exception $e) {
// We continue and return as this means that the element is not there or it is not the same.
* @return string Comma separated if multiple options are selected. Commas in option texts escaped with backslash.
*/
public function get_value() {
+ return $this->get_selected_options();
+ }
+
+ /**
+ * Returns whether the provided argument matches the current value.
+ *
+ * @param mixed $expectedvalue
+ * @return bool
+ */
+ public function matches($expectedvalue) {
+
+ $multiple = $this->field->hasAttribute('multiple');
+
+ // Same implementation as the parent if it is a single select.
+ if (!$multiple) {
+ if (trim($expectedvalue) != trim($this->get_value())) {
+ return false;
+ }
+ return true;
+ }
+
+ // We are dealing with a multi-select.
+
+ // Can pass multiple comma separated, with valuable commas escaped with backslash.
+ $expectedarr = array(); // Array of passed text options to test.
+
+ // Unescape + trim all options and flip it to have the expected values as keys.
+ $expectedoptions = $this->get_unescaped_options($expectedvalue);
+
+ // Get currently selected option's texts.
+ $texts = $this->get_selected_options(true);
+ $selectedoptiontexts = $this->get_unescaped_options($texts);
+
+ // Get currently selected option's values.
+ $values = $this->get_selected_options(false);
+ $selectedoptionvalues = $this->get_unescaped_options($values);
+
+ // Precheck to speed things up.
+ if (count($expectedoptions) !== count($selectedoptiontexts) ||
+ count($expectedoptions) !== count($selectedoptionvalues)) {
+ return false;
+ }
+
+ // We check against string-ordered lists of options.
+ if ($expectedoptions != $selectedoptiontexts &&
+ $expectedoptions != $selectedoptionvalues) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Cleans the list of options and returns it as a string separating options with |||.
+ *
+ * @param string $value The string containing the escaped options.
+ * @return string The options
+ */
+ protected function get_unescaped_options($value) {
+
+ // Can be multiple comma separated, with valuable commas escaped with backslash.
+ $optionsarray = array_map(
+ 'trim',
+ preg_replace('/\\\,/', ',',
+ preg_split('/(?<!\\\),/', $value)
+ )
+ );
+
+ // Sort by value (keeping the keys is irrelevant).
+ core_collator::asort($optionsarray, SORT_STRING);
+
+ // Returning it as a string which is easier to match against other values.
+ return implode('|||', $optionsarray);
+ }
+
+ /**
+ * Returns the field selected values.
+ *
+ * Externalized from the common behat_form_field API method get_value() as
+ * matches() needs to check against both values and texts.
+ *
+ * @param bool $returntexts Returns the options texts or the options values.
+ * @return string
+ */
+ protected function get_selected_options($returntexts = true) {
+
+ $method = 'getText';
+ if ($returntexts === false) {
+ $method = 'getValue';
+ }
// Is the select multiple?
$multiple = $this->field->hasAttribute('multiple');
if ($option->hasAttribute('selected')) {
if ($multiple) {
// If the select is multiple, text commas must be encoded.
- $selectedoptions[] = trim(str_replace(',', '\,', $option->getText()));
+ $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
} else {
- $selectedoptions[] = trim($option->getText());
+ $selectedoptions[] = trim($option->{$method}());
}
}
}
// Goutte does not keep the 'selected' attribute updated, but its getValue() returns
// the selected elements correctly, also those having commas within them.
} else {
+
+ // Goutte returns the values as an array or as a string depending
+ // on whether multiple options are selected or not.
$values = $this->field->getValue();
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+
// Get all the options in the select and extract their value/text pairs.
$alloptions = $this->field->findAll('xpath', '//option');
foreach ($alloptions as $option) {
if (in_array($option->getValue(), $values)) {
if ($multiple) {
// If the select is multiple, text commas must be encoded.
- $selectedoptions[] = trim(str_replace(',', '\,', $option->getText()));
+ $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}()));
} else {
- $selectedoptions[] = trim($option->getText());
+ $selectedoptions[] = trim($option->{$method}());
}
}
}
return implode(', ', $selectedoptions);
}
+
+ /**
+ * Returns the opton XPath based on it's select xpath.
+ *
+ * @param string $option
+ * @param string $selectxpath
+ * @return string xpath
+ */
+ protected function get_option_xpath($option, $selectxpath) {
+ $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option));
+ return $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]";
+ }
}
}
/**
- * Fills in form field with specified id|name|label|value.
+ * Fills in form text field with specified id|name|label|value. It works with text-based fields.
*
* @When /^I fill in "(?P<field_string>(?:[^"]|\\")*)" with "(?P<value_string>(?:[^"]|\\")*)"$/
* @throws ElementNotFoundException Thrown by behat_base::find
* @param string $value
*/
public function fill_field($field, $value) {
-
- $fieldnode = $this->find_field($field);
- $fieldnode->setValue($value);
+ $this->set_field_value($field, $value);
}
/**
* @param string $select
*/
public function select_option($option, $select) {
-
- $selectnode = $this->find_field($select);
-
- // We delegate to behat_form_field class, it will
- // guess the type properly as it is a select tag.
- $selectformfield = behat_field_manager::get_form_field($selectnode, $this->getSession());
- $selectformfield->set_value($option);
+ $this->set_field_value($select, $option);
}
/**
* @param string $radio The radio button id, name or label value
*/
public function select_radio($radio) {
-
- $radionode = $this->find_radio($radio);
- $radionode->check();
-
- // Adding a click as Selenium requires it to fire some JS events.
- if ($this->running_javascript()) {
- $radionode->click();
- }
+ $this->set_field_value($radio, 1);
}
/**
* @param string $option
*/
public function check_option($option) {
-
- // We don't delegate to behat_form_checkbox as the
- // step is explicitly saying I check.
- $checkboxnode = $this->find_field($option);
- $checkboxnode->check();
+ $this->set_field_value($option, 1);
}
/**
* @param string $option
*/
public function uncheck_option($option) {
-
- // We don't delegate to behat_form_checkbox as the
- // step is explicitly saying I uncheck.
- $checkboxnode = $this->find_field($option);
- $checkboxnode->uncheck();
+ $this->set_field_value($option, '');
}
/**
- * Checks that the form element field have the specified value.
- *
- * NOTE: This method/step does not support all fields. Namely, multi-select ones aren't supported.
- * @todo: MDL-43738 would try to put some better support here for that multi-select and others.
+ * Checks that the form element field matches the specified value. When using multi-select fields use commas to separate the selected options.
*
* @Then /^the "(?P<field_string>(?:[^"]|\\")*)" field should match "(?P<value_string>(?:[^"]|\\")*)" value$/
* @throws ExpectationException
// Get the field.
$field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
- $fieldvalue = $field->get_value();
// Checks if the provided value matches the current field value.
- if (trim($value) != trim($fieldvalue)) {
+ if (!$field->matches($value)) {
+ $fieldvalue = $field->get_value();
throw new ExpectationException(
'The \'' . $locator . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
$this->getSession()
}
}
+ /**
+ * Checks that the form element field does not match the specified value.
+ *
+ * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<value_string>(?:[^"]|\\")*)"$/
+ * @throws ExpectationException
+ * @throws ElementNotFoundException Thrown by behat_base::find
+ * @param string $field
+ * @param string $value
+ * @return void
+ */
+ public function the_field_does_not_match_value($field, $value) {
+
+ $fieldnode = $this->find_field($field);
+
+ // Get the field.
+ $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
+
+ // Checks if the provided value matches the current field value.
+ if ($field->matches($value)) {
+ $fieldvalue = $field->get_value();
+ throw new ExpectationException(
+ 'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
+ $this->getSession()
+ );
+ }
+ }
+
+ /**
+ * Checks if fields values matches the provided values. Provide a table with field/value data.
+ *
+ * @Then /^the following fields match these values:$/
+ * @throws ExpectationException
+ * @param TableNode $table Pairs of | field | value |
+ */
+ public function the_following_fields_match_these_values(TableNode $data) {
+
+ // Expand all fields in case we have.
+ $this->expand_all_fields();
+
+ $datahash = $data->getRowsHash();
+
+ // The action depends on the field type.
+ foreach ($datahash as $locator => $value) {
+ $this->the_field_should_match_value($locator, $value);
+ }
+ }
+
+ /**
+ * Checks that fields values do not match the provided values. Provide a table with field/value data.
+ *
+ * @Then /^the following fields do not match these values:$/
+ * @throws ExpectationException
+ * @param TableNode $table Pairs of | field | value |
+ */
+ public function the_following_fields_do_not_match_these_values(TableNode $data) {
+
+ // Expand all fields in case we have.
+ $this->expand_all_fields();
+
+ $datahash = $data->getRowsHash();
+
+ // The action depends on the field type.
+ foreach ($datahash as $locator => $value) {
+ $this->the_field_does_not_match_value($locator, $value);
+ }
+ }
+
/**
* Checks, that checkbox with specified in|name|label|value is checked.
*
* @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should be checked$/
- * @see Behat\MinkExtension\Context\MinkContext
* @param string $checkbox
*/
public function assert_checkbox_checked($checkbox) {
- $this->assertSession()->checkboxChecked($checkbox);
+ $this->the_field_should_match_value($checkbox, 1);
}
/**
* Checks, that checkbox with specified in|name|label|value is unchecked.
*
* @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should not be checked$/
- * @see Behat\MinkExtension\Context\MinkContext
* @param string $checkbox
*/
public function assert_checkbox_not_checked($checkbox) {
- $this->assertSession()->checkboxNotChecked($checkbox);
+ $this->the_field_should_match_value($checkbox, '');
}
/**
}
/**
- * Checks, that given select box contains the specified option selected.
+ * Generic field setter.
*
- * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)" selected$/
- * @throws ExpectationException
- * @throws ElementNotFoundException Thrown by behat_base::find
- * @param string $select The select element name
- * @param string $option The option text. Plain value or comma separated
- * values if multiple. Commas in multiple values escaped with backslash.
- */
- public function the_select_box_should_contain_selected($select, $option) {
-
- $selectnode = $this->find_field($select);
- $multiple = $selectnode->hasAttribute('multiple');
- $optionsarr = array(); // Array of passed text options to test.
- $selectedarr = array(); // Array of selected text options.
-
- if ($multiple) {
- // Can pass multiple comma separated, with valuable commas escaped with backslash.
- foreach (preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $option)) as $opt) {
- $optionsarr[] = trim($opt);
- }
- } else {
- // Only one option has been passed.
- $optionsarr[] = trim($option);
- }
-
- // Get currently selected texts.
- $field = behat_field_manager::get_form_field($selectnode, $this->getSession());
- $value = $field->get_value();
-
- if ($multiple) {
- // Can be multiple comma separated, with valuable commas escaped with backslash.
- foreach (preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $value)) as $val) {
- $selectedarr[] = trim($val);
- }
- } else {
- // Only one text can be selected.
- $selectedarr[] = trim($value);
- }
-
- // Everything normalized, Verify every option is a selected one.
- foreach ($optionsarr as $opt) {
- if (!in_array($opt, $selectedarr)) {
- throw new ExpectationException(
- 'The select box "' . $select . '" does not contain the option "' . $opt . '"' . ' selected',
- $this->getSession()
- );
- }
- }
- }
-
- /**
- * Checks, that given select box contains the specified option not selected.
+ * Internal API method, a generic *I set "VALUE" to "FIELD" field*
+ * could be created based on it.
*
- * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)" not selected$/
- * @throws ExpectationException
- * @throws ElementNotFoundException Thrown by behat_base::find
- * @param string $select The select element name
- * @param string $option The option text. Plain value or comma separated
- * values if multiple. Commas in multiple values escaped with backslash.
+ * @param string $fieldlocator The pointer to the field, it will depend on the field type.
+ * @param string $value
+ * @return void
*/
- public function the_select_box_should_contain_not_selected($select, $option) {
-
- $selectnode = $this->find_field($select);
- $multiple = $selectnode->hasAttribute('multiple');
- $optionsarr = array(); // Array of passed text options to test.
- $selectedarr = array(); // Array of selected text options.
-
- // First of all, the option(s) must exist, delegate it. Plain and raw.
- $this->the_select_box_should_contain($select, $option);
-
- if ($multiple) {
- // Can pass multiple comma separated, with valuable commas escaped with backslash.
- foreach (preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $option)) as $opt) {
- $optionsarr[] = trim($opt);
- }
- } else {
- // Only one option has been passed.
- $optionsarr[] = trim($option);
- }
-
- // Get currently selected texts.
- $field = behat_field_manager::get_form_field($selectnode, $this->getSession());
- $value = $field->get_value();
+ protected function set_field_value($fieldlocator, $value) {
- if ($multiple) {
- // Can be multiple comma separated, with valuable commas escaped with backslash.
- foreach (preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $value)) as $val) {
- $selectedarr[] = trim($val);
- }
- } else {
- // Only one text can be selected.
- $selectedarr[] = trim($value);
- }
+ $node = $this->find_field($fieldlocator);
- // Everything normalized, Verify every option is not a selected one.
- foreach ($optionsarr as $opt) {
- // Now, verify it's not selected.
- if (in_array($opt, $selectedarr)) {
- throw new ExpectationException(
- 'The select box "' . $select . '" contains the option "' . $opt . '"' . ' selected',
- $this->getSession()
- );
- }
- }
+ // We delegate to behat_form_field class, it will
+ // guess the type properly as it is a select tag.
+ $field = behat_field_manager::get_form_field($node, $this->getSession());
+ $field->set_value($value);
}
}