MDL-43713 behat: improve multi-select support
[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 moodle form with field/value data.
65      *
66      * @Given /^I fill the moodle form with:$/
67      * @throws ElementNotFoundException Thrown by behat_base::find
68      * @param TableNode $data
69      */
70     public function i_fill_the_moodle_form_with(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) {
80             // Getting the node element pointed by the label.
81             $fieldnode = $this->find_field($locator);
83             // Gets the field type from a parent node.
84             $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
86             // Delegates to the field class.
87             $field->set_value($value);
88         }
89     }
91     /**
92      * Expands all moodleform's fields, including collapsed fieldsets and advanced fields if they are present.
93      * @Given /^I expand all fieldsets$/
94      */
95     public function i_expand_all_fieldsets() {
96         $this->expand_all_fields();
97     }
99     /**
100      * Expands all moodle form fieldsets if they exists.
101      *
102      * Externalized from i_expand_all_fields to call it from
103      * other form-related steps without having to use steps-group calls.
104      *
105      * @throws ElementNotFoundException Thrown by behat_base::find_all
106      * @return void
107      */
108     protected function expand_all_fields() {
110         // We ensure that all the editors are loaded and we can interact with them.
111         $this->ensure_editors_are_loaded();
113         // We already know that we waited for the DOM and the JS to be loaded, even the editor
114         // so, we will use the reduced timeout as it is a common task and we should save time.
115         try {
117             // Expand fieldsets link.
118             $xpath = "//div[@class='collapsible-actions']" .
119                 "/descendant::a[contains(concat(' ', @class, ' '), ' collapseexpand ')]" .
120                 "[not(contains(concat(' ', @class, ' '), ' collapse-all '))]";
121             $collapseexpandlink = $this->find('xpath', $xpath, false, false, self::REDUCED_TIMEOUT);
122             $collapseexpandlink->click();
124         } catch (ElementNotFoundException $e) {
125             // The behat_base::find() method throws an exception if there are no elements,
126             // we should not fail a test because of this. We continue if there are not expandable fields.
127         }
129         // Different try & catch as we can have expanded fieldsets with advanced fields on them.
130         try {
132             // Expand all fields xpath.
133             $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
134                 "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
136             // We don't wait here as we already waited when getting the expand fieldsets links.
137             if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
138                 return;
139             }
141             // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern is added to the array
142             // with of xpaths with a [0], [1]... sufix, but when we click on an element it does not matches the specified xpath
143             // anymore (now is a "Show less..." link) so [1] becomes [0], that's why we always click on the first XPath match,
144             // will be always the next one.
145             $iterations = count($showmores);
146             for ($i = 0; $i < $iterations; $i++) {
147                 $showmores[0]->click();
148             }
150         } catch (ElementNotFoundException $e) {
151             // We continue with the test.
152         }
154     }
156     /**
157      * Fills in form field with specified id|name|label|value.
158      *
159      * @When /^I fill in "(?P<field_string>(?:[^"]|\\")*)" with "(?P<value_string>(?:[^"]|\\")*)"$/
160      * @throws ElementNotFoundException Thrown by behat_base::find
161      * @param string $field
162      * @param string $value
163      */
164     public function fill_field($field, $value) {
166         $fieldnode = $this->find_field($field);
167         $fieldnode->setValue($value);
168     }
170     /**
171      * Selects option in select field with specified id|name|label|value.
172      *
173      * @When /^I select "(?P<option_string>(?:[^"]|\\")*)" from "(?P<select_string>(?:[^"]|\\")*)"$/
174      * @throws ElementNotFoundException Thrown by behat_base::find
175      * @param string $option
176      * @param string $select
177      */
178     public function select_option($option, $select) {
180         $selectnode = $this->find_field($select);
182         // We delegate to behat_form_field class, it will
183         // guess the type properly as it is a select tag.
184         $selectformfield = behat_field_manager::get_form_field($selectnode, $this->getSession());
185         $selectformfield->set_value($option);
186     }
188     /**
189      * Selects the specified id|name|label from the specified radio button.
190      *
191      * @When /^I select "(?P<radio_button_string>(?:[^"]|\\")*)" radio button$/
192      * @throws ElementNotFoundException Thrown by behat_base::find
193      * @param string $radio The radio button id, name or label value
194      */
195     public function select_radio($radio) {
197         $radionode = $this->find_radio($radio);
198         $radionode->check();
200         // Adding a click as Selenium requires it to fire some JS events.
201         if ($this->running_javascript()) {
202             $radionode->click();
203         }
204     }
206     /**
207      * Checks checkbox with specified id|name|label|value.
208      *
209      * @When /^I check "(?P<option_string>(?:[^"]|\\")*)"$/
210      * @throws ElementNotFoundException Thrown by behat_base::find
211      * @param string $option
212      */
213     public function check_option($option) {
215         // We don't delegate to behat_form_checkbox as the
216         // step is explicitly saying I check.
217         $checkboxnode = $this->find_field($option);
218         $checkboxnode->check();
219     }
221     /**
222      * Unchecks checkbox with specified id|name|label|value.
223      *
224      * @When /^I uncheck "(?P<option_string>(?:[^"]|\\")*)"$/
225      * @throws ElementNotFoundException Thrown by behat_base::find
226      * @param string $option
227      */
228     public function uncheck_option($option) {
230         // We don't delegate to behat_form_checkbox as the
231         // step is explicitly saying I uncheck.
232         $checkboxnode = $this->find_field($option);
233         $checkboxnode->uncheck();
234     }
236     /**
237      * Checks that the form element field have the specified value.
238      *
239      * NOTE: This method/step does not support all fields. Namely, multi-select ones aren't supported.
240      * @todo: MDL-43738 would try to put some better support here for that multi-select and others.
241      *
242      * @Then /^the "(?P<field_string>(?:[^"]|\\")*)" field should match "(?P<value_string>(?:[^"]|\\")*)" value$/
243      * @throws ExpectationException
244      * @throws ElementNotFoundException Thrown by behat_base::find
245      * @param string $locator
246      * @param string $value
247      */
248     public function the_field_should_match_value($locator, $value) {
250         $fieldnode = $this->find_field($locator);
252         // Get the field.
253         $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
254         $fieldvalue = $field->get_value();
256         // Checks if the provided value matches the current field value.
257         if (trim($value) != trim($fieldvalue)) {
258             throw new ExpectationException(
259                 'The \'' . $locator . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
260                 $this->getSession()
261             );
262         }
263     }
265     /**
266      * Checks, that checkbox with specified in|name|label|value is checked.
267      *
268      * @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should be checked$/
269      * @see Behat\MinkExtension\Context\MinkContext
270      * @param string $checkbox
271      */
272     public function assert_checkbox_checked($checkbox) {
273         $this->assertSession()->checkboxChecked($checkbox);
274     }
276     /**
277      * Checks, that checkbox with specified in|name|label|value is unchecked.
278      *
279      * @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should not be checked$/
280      * @see Behat\MinkExtension\Context\MinkContext
281      * @param string $checkbox
282      */
283     public function assert_checkbox_not_checked($checkbox) {
284         $this->assertSession()->checkboxNotChecked($checkbox);
285     }
287     /**
288      * Checks, that given select box contains the specified option.
289      *
290      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
291      * @throws ExpectationException
292      * @throws ElementNotFoundException Thrown by behat_base::find
293      * @param string $select The select element name
294      * @param string $option The option text/value. Plain value or comma separated
295      *                       values if multiple. Commas in multiple values escaped with backslash.
296      */
297     public function the_select_box_should_contain($select, $option) {
299         $selectnode = $this->find_field($select);
300         $multiple = $selectnode->hasAttribute('multiple');
301         $optionsarr = array(); // Array of passed value/text options to test.
303         if ($multiple) {
304             // Can pass multiple comma separated, with valuable commas escaped with backslash.
305             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
306                 $optionsarr[] = trim($opt);
307             }
308         } else {
309             // Only one option has been passed.
310             $optionsarr[] = trim($option);
311         }
313         // Now get all the values and texts in the select.
314         $options = $selectnode->findAll('xpath', '//option');
315         $values = array();
316         foreach ($options as $opt) {
317             $values[trim($opt->getValue())] = trim($opt->getText());
318         }
320         foreach ($optionsarr as $opt) {
321             // Verify every option is a valid text or value.
322             if (!in_array($opt, $values) && !array_key_exists($opt, $values)) {
323                 throw new ExpectationException(
324                     'The select box "' . $select . '" does not contain the option "' . $opt . '"',
325                     $this->getSession()
326                 );
327             }
328         }
329     }
331     /**
332      * Checks, that given select box does not contain the specified option.
333      *
334      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
335      * @throws ExpectationException
336      * @throws ElementNotFoundException Thrown by behat_base::find
337      * @param string $select The select element name
338      * @param string $option The option text/value. Plain value or comma separated
339      *                       values if multiple. Commas in multiple values escaped with backslash.
340      */
341     public function the_select_box_should_not_contain($select, $option) {
343         $selectnode = $this->find_field($select);
344         $multiple = $selectnode->hasAttribute('multiple');
345         $optionsarr = array(); // Array of passed value/text options to test.
347         if ($multiple) {
348             // Can pass multiple comma separated, with valuable commas escaped with backslash.
349             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
350                 $optionsarr[] = trim($opt);
351             }
352         } else {
353             // Only one option has been passed.
354             $optionsarr[] = trim($option);
355         }
357         // Now get all the values and texts in the select.
358         $options = $selectnode->findAll('xpath', '//option');
359         $values = array();
360         foreach ($options as $opt) {
361             $values[trim($opt->getValue())] = trim($opt->getText());
362         }
364         foreach ($optionsarr as $opt) {
365             // Verify every option is not a valid text or value.
366             if (in_array($opt, $values) || array_key_exists($opt, $values)) {
367                 throw new ExpectationException(
368                     'The select box "' . $select . '" contains the option "' . $opt . '"',
369                     $this->getSession()
370                 );
371             }
372         }
373     }
375     /**
376      * Checks, that given select box contains the specified option selected.
377      *
378      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)" selected$/
379      * @throws ExpectationException
380      * @throws ElementNotFoundException Thrown by behat_base::find
381      * @param string $select The select element name
382      * @param string $option The option text. Plain value or comma separated
383      *                       values if multiple. Commas in multiple values escaped with backslash.
384      */
385     public function the_select_box_should_contain_selected($select, $option) {
387         $selectnode = $this->find_field($select);
388         $multiple = $selectnode->hasAttribute('multiple');
389         $optionsarr = array(); // Array of passed text options to test.
390         $selectedarr = array(); // Array of selected text options.
392         if ($multiple) {
393             // Can pass multiple comma separated, with valuable commas escaped with backslash.
394             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
395                 $optionsarr[] = trim($opt);
396             }
397         } else {
398             // Only one option has been passed.
399             $optionsarr[] = trim($option);
400         }
402         // Get currently selected texts.
403         $field = behat_field_manager::get_form_field($selectnode, $this->getSession());
404         $value = $field->get_value();
406         if ($multiple) {
407             // Can be multiple comma separated, with valuable commas escaped with backslash.
408             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $value)) as $val) {
409                 $selectedarr[] = trim($val);
410             }
411         } else {
412             // Only one text can be selected.
413             $selectedarr[] = trim($value);
414         }
416         // Everything normalized, Verify every option is a selected one.
417         foreach ($optionsarr as $opt) {
418             if (!in_array($opt, $selectedarr)) {
419                 throw new ExpectationException(
420                     'The select box "' . $select . '" does not contain the option "' . $opt . '"' . ' selected',
421                     $this->getSession()
422                 );
423             }
424         }
425     }
427     /**
428      * Checks, that given select box contains the specified option not selected.
429      *
430      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)" not selected$/
431      * @throws ExpectationException
432      * @throws ElementNotFoundException Thrown by behat_base::find
433      * @param string $select The select element name
434      * @param string $option The option text. Plain value or comma separated
435      *                       values if multiple. Commas in multiple values escaped with backslash.
436      */
437     public function the_select_box_should_contain_not_selected($select, $option) {
439         $selectnode = $this->find_field($select);
440         $multiple = $selectnode->hasAttribute('multiple');
441         $optionsarr = array(); // Array of passed text options to test.
442         $selectedarr = array(); // Array of selected text options.
444         // First of all, the option(s) must exist, delegate it. Plain and raw.
445         $this->the_select_box_should_contain($select, $option);
447         if ($multiple) {
448             // Can pass multiple comma separated, with valuable commas escaped with backslash.
449             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
450                 $optionsarr[] = trim($opt);
451             }
452         } else {
453             // Only one option has been passed.
454             $optionsarr[] = trim($option);
455         }
457         // Get currently selected texts.
458         $field = behat_field_manager::get_form_field($selectnode, $this->getSession());
459         $value = $field->get_value();
461         if ($multiple) {
462             // Can be multiple comma separated, with valuable commas escaped with backslash.
463             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $value)) as $val) {
464                 $selectedarr[] = trim($val);
465             }
466         } else {
467             // Only one text can be selected.
468             $selectedarr[] = trim($value);
469         }
471         // Everything normalized, Verify every option is not a selected one.
472         foreach ($optionsarr as $opt) {
473             // Now, verify it's not selected.
474             if (in_array($opt, $selectedarr)) {
475                 throw new ExpectationException(
476                     'The select box "' . $select . '" contains the option "' . $opt . '"' . ' selected',
477                     $this->getSession()
478                 );
479             }
480         }
481     }