MDL-49205 behat: fix I set the field with xpath fail message.
[moodle.git] / lib / tests / behat / behat_forms.php
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/>.
17 /**
18  * Steps definitions related with forms.
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  */
26 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
28 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
29 require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
31 use Behat\Behat\Context\Step\Given as Given,
32     Behat\Behat\Context\Step\When as When,
33     Behat\Behat\Context\Step\Then as Then,
34     Behat\Gherkin\Node\TableNode as TableNode,
35     Behat\Mink\Element\NodeElement as NodeElement,
36     Behat\Mink\Exception\ExpectationException as ExpectationException,
37     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
39 /**
40  * Forms-related steps definitions.
41  *
42  * @package    core
43  * @category   test
44  * @copyright  2012 David MonllaĆ³
45  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  */
47 class behat_forms extends behat_base {
49     /**
50      * Presses button with specified id|name|title|alt|value.
51      *
52      * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/
53      * @throws ElementNotFoundException Thrown by behat_base::find
54      * @param string $button
55      */
56     public function press_button($button) {
58         // Ensures the button is present.
59         $buttonnode = $this->find_button($button);
60         $buttonnode->press();
61     }
63     /**
64      * Fills a form with field/value data. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
65      *
66      * @Given /^I set the following fields to these values:$/
67      * @throws ElementNotFoundException Thrown by behat_base::find
68      * @param TableNode $data
69      */
70     public function i_set_the_following_fields_to_these_values(TableNode $data) {
72         // Expand all fields in case we have.
73         $this->expand_all_fields();
75         $datahash = $data->getRowsHash();
77         // The action depends on the field type.
78         foreach ($datahash as $locator => $value) {
79             $this->set_field_value($locator, $value);
80         }
81     }
83     /**
84      * Expands all moodleform's fields, including collapsed fieldsets and advanced fields if they are present.
85      * @Given /^I expand all fieldsets$/
86      */
87     public function i_expand_all_fieldsets() {
88         $this->expand_all_fields();
89     }
91     /**
92      * Expands all moodle form fieldsets if they exists.
93      *
94      * Externalized from i_expand_all_fields to call it from
95      * other form-related steps without having to use steps-group calls.
96      *
97      * @throws ElementNotFoundException Thrown by behat_base::find_all
98      * @return void
99      */
100     protected function expand_all_fields() {
101         // Expand only if JS mode, else not needed.
102         if (!$this->running_javascript()) {
103             return;
104         }
106         // We already know that we waited for the DOM and the JS to be loaded, even the editor
107         // so, we will use the reduced timeout as it is a common task and we should save time.
108         try {
110             // Expand fieldsets link.
111             $xpath = "//div[@class='collapsible-actions']" .
112                 "/descendant::a[contains(concat(' ', @class, ' '), ' collapseexpand ')]" .
113                 "[not(contains(concat(' ', @class, ' '), ' collapse-all '))]";
114             $collapseexpandlink = $this->find('xpath', $xpath, false, false, self::REDUCED_TIMEOUT);
115             $collapseexpandlink->click();
117         } catch (ElementNotFoundException $e) {
118             // The behat_base::find() method throws an exception if there are no elements,
119             // we should not fail a test because of this. We continue if there are not expandable fields.
120         }
122         // Different try & catch as we can have expanded fieldsets with advanced fields on them.
123         try {
125             // Expand all fields xpath.
126             $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
127                 "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
129             // We don't wait here as we already waited when getting the expand fieldsets links.
130             if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
131                 return;
132             }
134             // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern is added to the array
135             // with of xpaths with a [0], [1]... sufix, but when we click on an element it does not matches the specified xpath
136             // anymore (now is a "Show less..." link) so [1] becomes [0], that's why we always click on the first XPath match,
137             // will be always the next one.
138             $iterations = count($showmores);
139             for ($i = 0; $i < $iterations; $i++) {
140                 $showmores[0]->click();
141             }
143         } catch (ElementNotFoundException $e) {
144             // We continue with the test.
145         }
147     }
149     /**
150      * Sets the specified value to the field.
151      *
152      * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
153      * @throws ElementNotFoundException Thrown by behat_base::find
154      * @param string $field
155      * @param string $value
156      * @return void
157      */
158     public function i_set_the_field_to($field, $value) {
159         $this->set_field_value($field, $value);
160     }
162     /**
163      * Sets the specified value to the field with xpath.
164      *
165      * @Given /^I set the field with xpath "(?P<fieldxpath_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
166      * @throws ElementNotFoundException Thrown by behat_base::find
167      * @param string $field
168      * @param string $value
169      * @return void
170      */
171     public function i_set_the_field_with_xpath_to($fieldxpath, $value) {
172         $fieldNode = $this->find('xpath', $fieldxpath);
173         $field = behat_field_manager::get_form_field($fieldNode, $this->getSession());
174         $field->set_value($value);
175     }
177     /**
178      * Checks, the field matches the value. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
179      *
180      * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
181      * @throws ElementNotFoundException Thrown by behat_base::find
182      * @param string $field
183      * @param string $value
184      * @return void
185      */
186     public function the_field_matches_value($field, $value) {
188         // Get the field.
189         $formfield = behat_field_manager::get_form_field_from_label($field, $this);
191         // Checks if the provided value matches the current field value.
192         if (!$formfield->matches($value)) {
193             $fieldvalue = $formfield->get_value();
194             throw new ExpectationException(
195                 'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
196                 $this->getSession()
197             );
198         }
199     }
201     /**
202      * Checks, the field does not match the value. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
203      *
204      * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
205      * @throws ExpectationException
206      * @throws ElementNotFoundException Thrown by behat_base::find
207      * @param string $field
208      * @param string $value
209      * @return void
210      */
211     public function the_field_does_not_match_value($field, $value) {
213         // Get the field.
214         $formfield = behat_field_manager::get_form_field_from_label($field, $this);
216         // Checks if the provided value matches the current field value.
217         if ($formfield->matches($value)) {
218             $fieldvalue = $formfield->get_value();
219             throw new ExpectationException(
220                 'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
221                 $this->getSession()
222             );
223         }
224     }
226     /**
227      * Checks, the provided field/value matches. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
228      *
229      * @Then /^the following fields match these values:$/
230      * @throws ExpectationException
231      * @param TableNode $data Pairs of | field | value |
232      */
233     public function the_following_fields_match_these_values(TableNode $data) {
235         // Expand all fields in case we have.
236         $this->expand_all_fields();
238         $datahash = $data->getRowsHash();
240         // The action depends on the field type.
241         foreach ($datahash as $locator => $value) {
242             $this->the_field_matches_value($locator, $value);
243         }
244     }
246     /**
247      * Checks that the provided field/value pairs don't match. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
248      *
249      * @Then /^the following fields do not match these values:$/
250      * @throws ExpectationException
251      * @param TableNode $data Pairs of | field | value |
252      */
253     public function the_following_fields_do_not_match_these_values(TableNode $data) {
255         // Expand all fields in case we have.
256         $this->expand_all_fields();
258         $datahash = $data->getRowsHash();
260         // The action depends on the field type.
261         foreach ($datahash as $locator => $value) {
262             $this->the_field_does_not_match_value($locator, $value);
263         }
264     }
266     /**
267      * Checks, that given select box contains the specified option.
268      *
269      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
270      * @throws ExpectationException
271      * @throws ElementNotFoundException Thrown by behat_base::find
272      * @param string $select The select element name
273      * @param string $option The option text/value. Plain value or comma separated
274      *                       values if multiple. Commas in multiple values escaped with backslash.
275      */
276     public function the_select_box_should_contain($select, $option) {
278         $selectnode = $this->find_field($select);
279         $multiple = $selectnode->hasAttribute('multiple');
280         $optionsarr = array(); // Array of passed value/text options to test.
282         if ($multiple) {
283             // Can pass multiple comma separated, with valuable commas escaped with backslash.
284             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
285                 $optionsarr[] = trim($opt);
286             }
287         } else {
288             // Only one option has been passed.
289             $optionsarr[] = trim($option);
290         }
292         // Now get all the values and texts in the select.
293         $options = $selectnode->findAll('xpath', '//option');
294         $values = array();
295         foreach ($options as $opt) {
296             $values[trim($opt->getValue())] = trim($opt->getText());
297         }
299         foreach ($optionsarr as $opt) {
300             // Verify every option is a valid text or value.
301             if (!in_array($opt, $values) && !array_key_exists($opt, $values)) {
302                 throw new ExpectationException(
303                     'The select box "' . $select . '" does not contain the option "' . $opt . '"',
304                     $this->getSession()
305                 );
306             }
307         }
308     }
310     /**
311      * Checks, that given select box does not contain the specified option.
312      *
313      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
314      * @throws ExpectationException
315      * @throws ElementNotFoundException Thrown by behat_base::find
316      * @param string $select The select element name
317      * @param string $option The option text/value. Plain value or comma separated
318      *                       values if multiple. Commas in multiple values escaped with backslash.
319      */
320     public function the_select_box_should_not_contain($select, $option) {
322         $selectnode = $this->find_field($select);
323         $multiple = $selectnode->hasAttribute('multiple');
324         $optionsarr = array(); // Array of passed value/text options to test.
326         if ($multiple) {
327             // Can pass multiple comma separated, with valuable commas escaped with backslash.
328             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
329                 $optionsarr[] = trim($opt);
330             }
331         } else {
332             // Only one option has been passed.
333             $optionsarr[] = trim($option);
334         }
336         // Now get all the values and texts in the select.
337         $options = $selectnode->findAll('xpath', '//option');
338         $values = array();
339         foreach ($options as $opt) {
340             $values[trim($opt->getValue())] = trim($opt->getText());
341         }
343         foreach ($optionsarr as $opt) {
344             // Verify every option is not a valid text or value.
345             if (in_array($opt, $values) || array_key_exists($opt, $values)) {
346                 throw new ExpectationException(
347                     'The select box "' . $select . '" contains the option "' . $opt . '"',
348                     $this->getSession()
349                 );
350             }
351         }
352     }
354     /**
355      * Generic field setter.
356      *
357      * Internal API method, a generic *I set "VALUE" to "FIELD" field*
358      * could be created based on it.
359      *
360      * @param string $fieldlocator The pointer to the field, it will depend on the field type.
361      * @param string $value
362      * @return void
363      */
364     protected function set_field_value($fieldlocator, $value) {
366         // We delegate to behat_form_field class, it will
367         // guess the type properly as it is a select tag.
368         $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
369         $field->set_value($value);
370     }