MDL-50163 behat: Return subcontext from session
[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\Gherkin\Node\PyStringNode as PyStringNode,
36     Behat\Mink\Element\NodeElement as NodeElement,
37     Behat\Mink\Exception\ExpectationException as ExpectationException,
38     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
40 /**
41  * Forms-related steps definitions.
42  *
43  * @package    core
44  * @category   test
45  * @copyright  2012 David MonllaĆ³
46  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
47  */
48 class behat_forms extends behat_base {
50     /**
51      * Presses button with specified id|name|title|alt|value.
52      *
53      * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/
54      * @throws ElementNotFoundException Thrown by behat_base::find
55      * @param string $button
56      */
57     public function press_button($button) {
59         // Ensures the button is present.
60         $buttonnode = $this->find_button($button);
61         $buttonnode->press();
62     }
64     /**
65      * Fills a form with field/value data. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
66      *
67      * @Given /^I set the following fields to these values:$/
68      * @throws ElementNotFoundException Thrown by behat_base::find
69      * @param TableNode $data
70      */
71     public function i_set_the_following_fields_to_these_values(TableNode $data) {
73         // Expand all fields in case we have.
74         $this->expand_all_fields();
76         $datahash = $data->getRowsHash();
78         // The action depends on the field type.
79         foreach ($datahash as $locator => $value) {
80             $this->set_field_value($locator, $value);
81         }
82     }
84     /**
85      * Expands all moodleform's fields, including collapsed fieldsets and advanced fields if they are present.
86      * @Given /^I expand all fieldsets$/
87      */
88     public function i_expand_all_fieldsets() {
89         $this->expand_all_fields();
90     }
92     /**
93      * Expands all moodle form fieldsets if they exists.
94      *
95      * Externalized from i_expand_all_fields to call it from
96      * other form-related steps without having to use steps-group calls.
97      *
98      * @throws ElementNotFoundException Thrown by behat_base::find_all
99      * @return void
100      */
101     protected function expand_all_fields() {
102         // Expand only if JS mode, else not needed.
103         if (!$this->running_javascript()) {
104             return;
105         }
107         // We already know that we waited for the DOM and the JS to be loaded, even the editor
108         // so, we will use the reduced timeout as it is a common task and we should save time.
109         try {
111             // Expand fieldsets link.
112             $xpath = "//div[@class='collapsible-actions']" .
113                 "/descendant::a[contains(concat(' ', @class, ' '), ' collapseexpand ')]" .
114                 "[not(contains(concat(' ', @class, ' '), ' collapse-all '))]";
115             $collapseexpandlink = $this->find('xpath', $xpath, false, false, self::REDUCED_TIMEOUT);
116             $collapseexpandlink->click();
118         } catch (ElementNotFoundException $e) {
119             // The behat_base::find() method throws an exception if there are no elements,
120             // we should not fail a test because of this. We continue if there are not expandable fields.
121         }
123         // Different try & catch as we can have expanded fieldsets with advanced fields on them.
124         try {
126             // Expand all fields xpath.
127             $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
128                 "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
130             // We don't wait here as we already waited when getting the expand fieldsets links.
131             if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
132                 return;
133             }
135             // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern is added to the array
136             // with of xpaths with a [0], [1]... sufix, but when we click on an element it does not matches the specified xpath
137             // anymore (now is a "Show less..." link) so [1] becomes [0], that's why we always click on the first XPath match,
138             // will be always the next one.
139             $iterations = count($showmores);
140             for ($i = 0; $i < $iterations; $i++) {
141                 $showmores[0]->click();
142             }
144         } catch (ElementNotFoundException $e) {
145             // We continue with the test.
146         }
148     }
150     /**
151      * Sets the specified value to the field.
152      *
153      * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
154      * @throws ElementNotFoundException Thrown by behat_base::find
155      * @param string $field
156      * @param string $value
157      * @return void
158      */
159     public function i_set_the_field_to($field, $value) {
160         $this->set_field_value($field, $value);
161     }
163     /**
164      * Press the key in the field to trigger the javascript keypress event
165      *
166      * Note that the character key will not actually be typed in the input field
167      *
168      * @Given /^I press key "(?P<key_string>(?:[^"]|\\")*)" in the field "(?P<field_string>(?:[^"]|\\")*)"$/
169      * @throws ElementNotFoundException Thrown by behat_base::find
170      * @param string $key either char-code or character itself,
171      *          may optionally be prefixed with ctrl-, alt-, shift- or meta-
172      * @param string $field
173      * @return void
174      */
175     public function i_press_key_in_the_field($key, $field) {
176         if (!$this->running_javascript()) {
177             throw new DriverException('Key press step is not available with Javascript disabled');
178         }
179         $fld = behat_field_manager::get_form_field_from_label($field, $this);
180         $modifier = null;
181         $char = $key;
182         if (preg_match('/-/', $key)) {
183             list($modifier, $char) = preg_split('/-/', $key, 2);
184         }
185         if (is_numeric($char)) {
186             $char = (int)$char;
187         }
188         $fld->key_press($char, $modifier);
189     }
191     /**
192      * Sets the specified value to the field.
193      *
194      * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to multiline$/
195      * @throws ElementNotFoundException Thrown by behat_base::find
196      * @param string $field
197      * @param PyStringNode $value
198      * @return void
199      */
200     public function i_set_the_field_to_multiline($field, PyStringNode $value) {
201         $this->set_field_value($field, (string)$value);
202     }
204     /**
205      * Sets the specified value to the field with xpath.
206      *
207      * @Given /^I set the field with xpath "(?P<fieldxpath_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
208      * @throws ElementNotFoundException Thrown by behat_base::find
209      * @param string $field
210      * @param string $value
211      * @return void
212      */
213     public function i_set_the_field_with_xpath_to($fieldxpath, $value) {
214         $fieldNode = $this->find('xpath', $fieldxpath);
215         $field = behat_field_manager::get_form_field($fieldNode, $this->getSession());
216         $field->set_value($value);
217     }
219     /**
220      * Checks, the field matches the value. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
221      *
222      * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
223      * @throws ElementNotFoundException Thrown by behat_base::find
224      * @param string $field
225      * @param string $value
226      * @return void
227      */
228     public function the_field_matches_value($field, $value) {
230         // Get the field.
231         $formfield = behat_field_manager::get_form_field_from_label($field, $this);
233         // Checks if the provided value matches the current field value.
234         if (!$formfield->matches($value)) {
235             $fieldvalue = $formfield->get_value();
236             throw new ExpectationException(
237                 'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
238                 $this->getSession()
239             );
240         }
241     }
243     /**
244      * Checks, the field does not match the value. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
245      *
246      * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
247      * @throws ExpectationException
248      * @throws ElementNotFoundException Thrown by behat_base::find
249      * @param string $field
250      * @param string $value
251      * @return void
252      */
253     public function the_field_does_not_match_value($field, $value) {
255         // Get the field.
256         $formfield = behat_field_manager::get_form_field_from_label($field, $this);
258         // Checks if the provided value matches the current field value.
259         if ($formfield->matches($value)) {
260             throw new ExpectationException(
261                 'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
262                 $this->getSession()
263             );
264         }
265     }
267     /**
268      * Checks, the field matches the value.
269      *
270      * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
271      * @throws ExpectationException
272      * @throws ElementNotFoundException Thrown by behat_base::find
273      * @param string $fieldxpath
274      * @param string $value
275      * @return void
276      */
277     public function the_field_with_xpath_matches_value($fieldxpath, $value) {
279         // Get the field.
280         $fieldnode = $this->find('xpath', $fieldxpath);
281         $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
283         // Checks if the provided value matches the current field value.
284         if (!$formfield->matches($value)) {
285             $fieldvalue = $formfield->get_value();
286             throw new ExpectationException(
287                 'The \'' . $fieldxpath . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
288                 $this->getSession()
289             );
290         }
291     }
293     /**
294      * Checks, the field does not match the value.
295      *
296      * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
297      * @throws ExpectationException
298      * @throws ElementNotFoundException Thrown by behat_base::find
299      * @param string $fieldxpath
300      * @param string $value
301      * @return void
302      */
303     public function the_field_with_xpath_does_not_match_value($fieldxpath, $value) {
305         // Get the field.
306         $fieldnode = $this->find('xpath', $fieldxpath);
307         $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
309         // Checks if the provided value matches the current field value.
310         if ($formfield->matches($value)) {
311             throw new ExpectationException(
312                 'The \'' . $fieldxpath . '\' value matches \'' . $value . '\' and it should not match it' ,
313                 $this->getSession()
314             );
315         }
316     }
318     /**
319      * Checks, the provided field/value matches. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
320      *
321      * @Then /^the following fields match these values:$/
322      * @throws ExpectationException
323      * @param TableNode $data Pairs of | field | value |
324      */
325     public function the_following_fields_match_these_values(TableNode $data) {
327         // Expand all fields in case we have.
328         $this->expand_all_fields();
330         $datahash = $data->getRowsHash();
332         // The action depends on the field type.
333         foreach ($datahash as $locator => $value) {
334             $this->the_field_matches_value($locator, $value);
335         }
336     }
338     /**
339      * Checks that the provided field/value pairs don't match. More info in http://docs.moodle.org/dev/Acceptance_testing#Providing_values_to_steps.
340      *
341      * @Then /^the following fields do not match these values:$/
342      * @throws ExpectationException
343      * @param TableNode $data Pairs of | field | value |
344      */
345     public function the_following_fields_do_not_match_these_values(TableNode $data) {
347         // Expand all fields in case we have.
348         $this->expand_all_fields();
350         $datahash = $data->getRowsHash();
352         // The action depends on the field type.
353         foreach ($datahash as $locator => $value) {
354             $this->the_field_does_not_match_value($locator, $value);
355         }
356     }
358     /**
359      * Checks, that given select box contains the specified option.
360      *
361      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
362      * @throws ExpectationException
363      * @throws ElementNotFoundException Thrown by behat_base::find
364      * @param string $select The select element name
365      * @param string $option The option text/value. Plain value or comma separated
366      *                       values if multiple. Commas in multiple values escaped with backslash.
367      */
368     public function the_select_box_should_contain($select, $option) {
370         $selectnode = $this->find_field($select);
371         $multiple = $selectnode->hasAttribute('multiple');
372         $optionsarr = array(); // Array of passed value/text options to test.
374         if ($multiple) {
375             // Can pass multiple comma separated, with valuable commas escaped with backslash.
376             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
377                 $optionsarr[] = trim($opt);
378             }
379         } else {
380             // Only one option has been passed.
381             $optionsarr[] = trim($option);
382         }
384         // Now get all the values and texts in the select.
385         $options = $selectnode->findAll('xpath', '//option');
386         $values = array();
387         foreach ($options as $opt) {
388             $values[trim($opt->getValue())] = trim($opt->getText());
389         }
391         foreach ($optionsarr as $opt) {
392             // Verify every option is a valid text or value.
393             if (!in_array($opt, $values) && !array_key_exists($opt, $values)) {
394                 throw new ExpectationException(
395                     'The select box "' . $select . '" does not contain the option "' . $opt . '"',
396                     $this->getSession()
397                 );
398             }
399         }
400     }
402     /**
403      * Checks, that given select box does not contain the specified option.
404      *
405      * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
406      * @throws ExpectationException
407      * @throws ElementNotFoundException Thrown by behat_base::find
408      * @param string $select The select element name
409      * @param string $option The option text/value. Plain value or comma separated
410      *                       values if multiple. Commas in multiple values escaped with backslash.
411      */
412     public function the_select_box_should_not_contain($select, $option) {
414         $selectnode = $this->find_field($select);
415         $multiple = $selectnode->hasAttribute('multiple');
416         $optionsarr = array(); // Array of passed value/text options to test.
418         if ($multiple) {
419             // Can pass multiple comma separated, with valuable commas escaped with backslash.
420             foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
421                 $optionsarr[] = trim($opt);
422             }
423         } else {
424             // Only one option has been passed.
425             $optionsarr[] = trim($option);
426         }
428         // Now get all the values and texts in the select.
429         $options = $selectnode->findAll('xpath', '//option');
430         $values = array();
431         foreach ($options as $opt) {
432             $values[trim($opt->getValue())] = trim($opt->getText());
433         }
435         foreach ($optionsarr as $opt) {
436             // Verify every option is not a valid text or value.
437             if (in_array($opt, $values) || array_key_exists($opt, $values)) {
438                 throw new ExpectationException(
439                     'The select box "' . $select . '" contains the option "' . $opt . '"',
440                     $this->getSession()
441                 );
442             }
443         }
444     }
446     /**
447      * Generic field setter.
448      *
449      * Internal API method, a generic *I set "VALUE" to "FIELD" field*
450      * could be created based on it.
451      *
452      * @param string $fieldlocator The pointer to the field, it will depend on the field type.
453      * @param string $value
454      * @return void
455      */
456     protected function set_field_value($fieldlocator, $value) {
458         // We delegate to behat_form_field class, it will
459         // guess the type properly as it is a select tag.
460         $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
461         $field->set_value($value);
462     }
464     /**
465      * Select a value from single select and redirect.
466      *
467      * @Given /^I select "(?P<singleselect_option_string>(?:[^"]|\\")*)" from the "(?P<singleselect_name_string>(?:[^"]|\\")*)" singleselect$/
468      */
469     public function i_select_from_the_singleselect($option, $singleselect) {
470         $actions = array(
471             new Given('I set the field "' . $this->escape($singleselect) . '" to "' . $this->escape($option) . '"'),
472         );
474         if (!$this->running_javascript()) {
475             // Press button in the specified select container.
476             $containerxpath = "//div[" .
477                 "(contains(concat(' ', normalize-space(@class), ' '), ' singleselect ') " .
478                     "or contains(concat(' ', normalize-space(@class), ' '), ' urlselect ')".
479                 ") and (
480                 .//label[contains(normalize-space(string(.)), '" . $singleselect . "')] " .
481                     "or .//select[(./@name='" . $singleselect . "' or ./@id='". $singleselect . "')]" .
482                 ")]";
484             $actions[] = new Given('I click on "' . get_string('go') . '" "button" in the "' . $containerxpath .
485                 '" "xpath_element"');
486         }
488         return $actions;
489     }