MDL-43713 behat: improve multi-select support
[moodle.git] / lib / tests / behat / behat_forms.php
CommitLineData
23ebc481
DM
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 * 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 */
25
26// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
27
28require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
a4534dce 29require_once(__DIR__ . '/../../../lib/behat/behat_field_manager.php');
23ebc481
DM
30
31use 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;
38
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 */
47class behat_forms extends behat_base {
48
49 /**
50 * Presses button with specified id|name|title|alt|value.
51 *
23ebc481 52 * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/
1f9ffbdb 53 * @throws ElementNotFoundException Thrown by behat_base::find
46ac40cd 54 * @param string $button
23ebc481
DM
55 */
56 public function press_button($button) {
1f9ffbdb
DM
57
58 // Ensures the button is present.
59 $buttonnode = $this->find_button($button);
60 $buttonnode->press();
23ebc481
DM
61 }
62
63 /**
64 * Fills a moodle form with field/value data.
65 *
23ebc481 66 * @Given /^I fill the moodle form with:$/
1f9ffbdb 67 * @throws ElementNotFoundException Thrown by behat_base::find
23ebc481
DM
68 * @param TableNode $data
69 */
70 public function i_fill_the_moodle_form_with(TableNode $data) {
71
f8f1bdc3
DM
72 // Expand all fields in case we have.
73 $this->expand_all_fields();
74
23ebc481
DM
75 $datahash = $data->getRowsHash();
76
77 // The action depends on the field type.
78 foreach ($datahash as $locator => $value) {
79
a4534dce 80 // Getting the node element pointed by the label.
1f9ffbdb 81 $fieldnode = $this->find_field($locator);
23ebc481
DM
82
83 // Gets the field type from a parent node.
051e9663 84 $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
23ebc481
DM
85
86 // Delegates to the field class.
d1e55a47 87 $field->set_value($value);
23ebc481
DM
88 }
89 }
90
f8f1bdc3
DM
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 }
98
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() {
109
fb99ef1d
DM
110 // We ensure that all the editors are loaded and we can interact with them.
111 $this->ensure_editors_are_loaded();
112
74c78e74
DM
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.
f8f1bdc3
DM
115 try {
116
8cda2e6f 117 // Expand fieldsets link.
74c78e74 118 $xpath = "//div[@class='collapsible-actions']" .
8cda2e6f 119 "/descendant::a[contains(concat(' ', @class, ' '), ' collapseexpand ')]" .
74c78e74
DM
120 "[not(contains(concat(' ', @class, ' '), ' collapse-all '))]";
121 $collapseexpandlink = $this->find('xpath', $xpath, false, false, self::REDUCED_TIMEOUT);
8cda2e6f 122 $collapseexpandlink->click();
f8f1bdc3
DM
123
124 } catch (ElementNotFoundException $e) {
74c78e74
DM
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.
f8f1bdc3
DM
127 }
128
129 // Different try & catch as we can have expanded fieldsets with advanced fields on them.
130 try {
131
8cda2e6f
DM
132 // Expand all fields xpath.
133 $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
134 "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
f8f1bdc3 135
8cda2e6f 136 // We don't wait here as we already waited when getting the expand fieldsets links.
74c78e74
DM
137 if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
138 return;
139 }
f8f1bdc3 140
8cda2e6f
DM
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.
f8f1bdc3
DM
145 $iterations = count($showmores);
146 for ($i = 0; $i < $iterations; $i++) {
147 $showmores[0]->click();
148 }
149
150 } catch (ElementNotFoundException $e) {
151 // We continue with the test.
152 }
153
154 }
155
23ebc481
DM
156 /**
157 * Fills in form field with specified id|name|label|value.
158 *
23ebc481 159 * @When /^I fill in "(?P<field_string>(?:[^"]|\\")*)" with "(?P<value_string>(?:[^"]|\\")*)"$/
1f9ffbdb 160 * @throws ElementNotFoundException Thrown by behat_base::find
46ac40cd
DM
161 * @param string $field
162 * @param string $value
23ebc481
DM
163 */
164 public function fill_field($field, $value) {
1f9ffbdb
DM
165
166 $fieldnode = $this->find_field($field);
167 $fieldnode->setValue($value);
23ebc481
DM
168 }
169
170 /**
171 * Selects option in select field with specified id|name|label|value.
172 *
23ebc481 173 * @When /^I select "(?P<option_string>(?:[^"]|\\")*)" from "(?P<select_string>(?:[^"]|\\")*)"$/
1f9ffbdb 174 * @throws ElementNotFoundException Thrown by behat_base::find
46ac40cd
DM
175 * @param string $option
176 * @param string $select
23ebc481
DM
177 */
178 public function select_option($option, $select) {
23ebc481 179
1f9ffbdb 180 $selectnode = $this->find_field($select);
28abad1a 181
d1e55a47
DM
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);
23ebc481
DM
186 }
187
fb624374
DM
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
5cde7298 193 * @param string $radio The radio button id, name or label value
fb624374
DM
194 */
195 public function select_radio($radio) {
196
197 $radionode = $this->find_radio($radio);
198 $radionode->check();
199
200 // Adding a click as Selenium requires it to fire some JS events.
cd7ea8f0
DM
201 if ($this->running_javascript()) {
202 $radionode->click();
203 }
fb624374
DM
204 }
205
23ebc481
DM
206 /**
207 * Checks checkbox with specified id|name|label|value.
208 *
23ebc481 209 * @When /^I check "(?P<option_string>(?:[^"]|\\")*)"$/
1f9ffbdb 210 * @throws ElementNotFoundException Thrown by behat_base::find
46ac40cd 211 * @param string $option
23ebc481
DM
212 */
213 public function check_option($option) {
1f9ffbdb 214
d1e55a47
DM
215 // We don't delegate to behat_form_checkbox as the
216 // step is explicitly saying I check.
1f9ffbdb
DM
217 $checkboxnode = $this->find_field($option);
218 $checkboxnode->check();
23ebc481
DM
219 }
220
221 /**
222 * Unchecks checkbox with specified id|name|label|value.
223 *
23ebc481 224 * @When /^I uncheck "(?P<option_string>(?:[^"]|\\")*)"$/
1f9ffbdb 225 * @throws ElementNotFoundException Thrown by behat_base::find
46ac40cd 226 * @param string $option
23ebc481
DM
227 */
228 public function uncheck_option($option) {
1f9ffbdb 229
d1e55a47
DM
230 // We don't delegate to behat_form_checkbox as the
231 // step is explicitly saying I uncheck.
1f9ffbdb
DM
232 $checkboxnode = $this->find_field($option);
233 $checkboxnode->uncheck();
23ebc481
DM
234 }
235
236 /**
237 * Checks that the form element field have the specified value.
238 *
5f66d46e
EL
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 *
23ebc481 242 * @Then /^the "(?P<field_string>(?:[^"]|\\")*)" field should match "(?P<value_string>(?:[^"]|\\")*)" value$/
1f9ffbdb
DM
243 * @throws ExpectationException
244 * @throws ElementNotFoundException Thrown by behat_base::find
46ac40cd
DM
245 * @param string $locator
246 * @param string $value
23ebc481
DM
247 */
248 public function the_field_should_match_value($locator, $value) {
249
1f9ffbdb 250 $fieldnode = $this->find_field($locator);
23ebc481 251
49d91129 252 // Get the field.
051e9663 253 $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
49d91129 254 $fieldvalue = $field->get_value();
23ebc481
DM
255
256 // Checks if the provided value matches the current field value.
15c7fb4a 257 if (trim($value) != trim($fieldvalue)) {
23ebc481 258 throw new ExpectationException(
49d91129 259 'The \'' . $locator . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
23ebc481
DM
260 $this->getSession()
261 );
262 }
263 }
264
265 /**
266 * Checks, that checkbox with specified in|name|label|value is checked.
267 *
23ebc481 268 * @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should be checked$/
1f9ffbdb 269 * @see Behat\MinkExtension\Context\MinkContext
46ac40cd 270 * @param string $checkbox
23ebc481
DM
271 */
272 public function assert_checkbox_checked($checkbox) {
23ebc481
DM
273 $this->assertSession()->checkboxChecked($checkbox);
274 }
275
276 /**
277 * Checks, that checkbox with specified in|name|label|value is unchecked.
278 *
23ebc481 279 * @Then /^the "(?P<checkbox_string>(?:[^"]|\\")*)" checkbox should not be checked$/
1f9ffbdb 280 * @see Behat\MinkExtension\Context\MinkContext
46ac40cd 281 * @param string $checkbox
23ebc481
DM
282 */
283 public function assert_checkbox_not_checked($checkbox) {
23ebc481
DM
284 $this->assertSession()->checkboxNotChecked($checkbox);
285 }
286
287 /**
288 * Checks, that given select box contains the specified option.
289 *
23ebc481 290 * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
1f9ffbdb
DM
291 * @throws ExpectationException
292 * @throws ElementNotFoundException Thrown by behat_base::find
23ebc481 293 * @param string $select The select element name
5f66d46e
EL
294 * @param string $option The option text/value. Plain value or comma separated
295 * values if multiple. Commas in multiple values escaped with backslash.
23ebc481
DM
296 */
297 public function the_select_box_should_contain($select, $option) {
298
1f9ffbdb 299 $selectnode = $this->find_field($select);
5f66d46e
EL
300 $multiple = $selectnode->hasAttribute('multiple');
301 $optionsarr = array(); // Array of passed value/text options to test.
23ebc481 302
5f66d46e
EL
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 }
312
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());
23ebc481
DM
318 }
319
5f66d46e
EL
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 }
23ebc481
DM
329 }
330
331 /**
332 * Checks, that given select box does not contain the specified option.
333 *
23ebc481 334 * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
1f9ffbdb
DM
335 * @throws ExpectationException
336 * @throws ElementNotFoundException Thrown by behat_base::find
23ebc481 337 * @param string $select The select element name
5f66d46e
EL
338 * @param string $option The option text/value. Plain value or comma separated
339 * values if multiple. Commas in multiple values escaped with backslash.
23ebc481
DM
340 */
341 public function the_select_box_should_not_contain($select, $option) {
342
1f9ffbdb 343 $selectnode = $this->find_field($select);
5f66d46e
EL
344 $multiple = $selectnode->hasAttribute('multiple');
345 $optionsarr = array(); // Array of passed value/text options to test.
23ebc481 346
5f66d46e
EL
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 }
356
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 }
363
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 }
374
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) {
386
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.
391
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 }
401
402 // Get currently selected texts.
403 $field = behat_field_manager::get_form_field($selectnode, $this->getSession());
404 $value = $field->get_value();
405
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 }
415
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 }
426
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) {
438
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.
443
444 // First of all, the option(s) must exist, delegate it. Plain and raw.
445 $this->the_select_box_should_contain($select, $option);
446
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 }
456
457 // Get currently selected texts.
458 $field = behat_field_manager::get_form_field($selectnode, $this->getSession());
459 $value = $field->get_value();
460
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 }
470
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 }
23ebc481
DM
480 }
481 }
482
23ebc481 483}