Commit | Line | Data |
---|---|---|
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 | * Single select form field class. | |
19 | * | |
20 | * @package core_form | |
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 | ||
28 | require_once(__DIR__ . '/behat_form_field.php'); | |
29 | ||
30 | /** | |
31 | * Single select form field. | |
32 | * | |
33 | * @package core_form | |
34 | * @category test | |
35 | * @copyright 2012 David Monllaó | |
36 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
37 | */ | |
38 | class behat_form_select extends behat_form_field { | |
39 | ||
40 | /** | |
5f66d46e | 41 | * Sets the value(s) of a select element. |
23ebc481 | 42 | * |
d1e55a47 DM |
43 | * Seems an easy select, but there are lots of combinations |
44 | * of browsers and operative systems and each one manages the | |
5f66d46e | 45 | * autosubmits and the multiple option selects in a different way. |
d1e55a47 | 46 | * |
5f66d46e | 47 | * @param string $value plain value or comma separated values if multiple. Commas in values escaped with backslash. |
23ebc481 DM |
48 | * @return void |
49 | */ | |
50 | public function set_value($value) { | |
28abad1a | 51 | |
d1e55a47 DM |
52 | // In some browsers we select an option and it triggers all the |
53 | // autosubmits and works as expected but not in all of them, so we | |
54 | // try to catch all the possibilities to make this function work as | |
55 | // expected. | |
56 | ||
57 | // Get the internal id of the element we are going to click. | |
58 | // This kind of internal IDs are only available in the selenium wire | |
59 | // protocol, so only available using selenium drivers, phantomjs and family. | |
28abad1a | 60 | if ($this->running_javascript()) { |
d1e55a47 DM |
61 | $currentelementid = $this->get_internal_field_id(); |
62 | } | |
28abad1a | 63 | |
5f66d46e EL |
64 | // Is the select multiple? |
65 | $multiple = $this->field->hasAttribute('multiple'); | |
66 | ||
67 | // By default, assume the passed value is a non-multiple option. | |
68 | $options = array(trim($value)); | |
69 | ||
70 | // Here we select the option(s). | |
71 | if ($multiple) { | |
72 | // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash. | |
73 | $options = preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $value)); | |
74 | // This is a multiple select, let's pass the multiple flag after first option. | |
75 | $afterfirstoption = false; | |
76 | foreach ($options as $option) { | |
77 | $this->field->selectOption(trim($option), $afterfirstoption); | |
78 | $afterfirstoption = true; | |
79 | } | |
80 | } else { | |
81 | // This is a single select, let's pass the last one specified. | |
82 | $this->field->selectOption(end($options)); | |
83 | } | |
1fb97157 | 84 | |
d1e55a47 DM |
85 | // With JS disabled this is enough and we finish here. |
86 | if (!$this->running_javascript()) { | |
87 | return; | |
88 | } | |
38976081 | 89 | |
d1e55a47 DM |
90 | // With JS enabled we add more clicks as some selenium |
91 | // drivers requires it to fire JS events. | |
38976081 | 92 | |
d1e55a47 DM |
93 | // In some browsers the selectOption actions can perform a form submit or reload page |
94 | // so we need to ensure the element is still available to continue interacting | |
95 | // with it. We don't wait here. | |
96 | $selectxpath = $this->field->getXpath(); | |
97 | if (!$this->session->getDriver()->find($selectxpath)) { | |
98 | return; | |
99 | } | |
28abad1a | 100 | |
d1e55a47 DM |
101 | // We also check the selenium internal element id, if it have changed |
102 | // we are dealing with an autosubmit that was already executed, and we don't to | |
103 | // execute anything else as the action we wanted was already performed. | |
104 | if ($currentelementid != $this->get_internal_field_id()) { | |
105 | return; | |
106 | } | |
107 | ||
5f66d46e EL |
108 | // We also check that the option(s) are still there. We neither wait. |
109 | foreach ($options as $option) { | |
110 | $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option)); | |
111 | $optionxpath = $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]"; | |
112 | if (!$this->session->getDriver()->find($optionxpath)) { | |
113 | return; | |
114 | } | |
d1e55a47 DM |
115 | } |
116 | ||
405cdd04 DM |
117 | // Wrapped in try & catch as the element may disappear if an AJAX request was submitted. |
118 | try { | |
119 | $multiple = $this->field->hasAttribute('multiple'); | |
120 | } catch (Exception $e) { | |
121 | // We do not specify any specific Exception type as there are | |
122 | // different exceptions that can be thrown by the driver and | |
123 | // we can not control them all, also depending on the selenium | |
124 | // version the exception type can change. | |
125 | return; | |
126 | } | |
127 | ||
128 | // Wait for all the possible AJAX requests that have been | |
129 | // already triggered by selectOption() to be finished. | |
130 | $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); | |
131 | ||
d1e55a47 | 132 | // Single select sometimes needs an extra click in the option. |
405cdd04 | 133 | if (!$multiple) { |
d1e55a47 DM |
134 | |
135 | // Using the driver direcly because Element methods are messy when dealing | |
136 | // with elements inside containers. | |
137 | $optionnodes = $this->session->getDriver()->find($optionxpath); | |
138 | if ($optionnodes) { | |
fff500c7 DM |
139 | // Wrapped in a try & catch as we can fall into race conditions |
140 | // and the element may not be there. | |
141 | try { | |
142 | current($optionnodes)->click(); | |
143 | } catch (Exception $e) { | |
144 | // We continue and return as this means that the element is not there or it is not the same. | |
145 | return; | |
146 | } | |
28abad1a | 147 | } |
d1e55a47 DM |
148 | |
149 | } else { | |
afe9f42a | 150 | |
fff500c7 DM |
151 | // Wrapped in a try & catch as we can fall into race conditions |
152 | // and the element may not be there. | |
153 | try { | |
154 | // Multiple ones needs the click in the select. | |
155 | $this->field->click(); | |
156 | } catch (Exception $e) { | |
157 | // We continue and return as this means that the element is not there or it is not the same. | |
158 | return; | |
159 | } | |
d1e55a47 DM |
160 | |
161 | // We ensure that the option is still there. | |
162 | if (!$this->session->getDriver()->find($optionxpath)) { | |
163 | return; | |
164 | } | |
165 | ||
afe9f42a DM |
166 | // Wait for all the possible AJAX requests that have been |
167 | // already triggered by selectOption() to be finished. | |
168 | $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); | |
169 | ||
fff500c7 DM |
170 | // Wrapped in a try & catch as we can fall into race conditions |
171 | // and the element may not be there. | |
172 | try { | |
5f66d46e | 173 | // Repeating the select(s) as some drivers (chrome that I know) are moving |
fff500c7 | 174 | // to another option after the general select field click above. |
5f66d46e EL |
175 | foreach ($options as $option) { |
176 | $this->field->selectOption(trim($option), true); | |
177 | } | |
fff500c7 DM |
178 | } catch (Exception $e) { |
179 | // We continue and return as this means that the element is not there or it is not the same. | |
180 | return; | |
181 | } | |
28abad1a | 182 | } |
23ebc481 DM |
183 | } |
184 | ||
185 | /** | |
5f66d46e | 186 | * Returns the text of the currently selected options. |
23ebc481 | 187 | * |
5f66d46e | 188 | * @return string Comma separated if multiple options are selected. Commas in option texts escaped with backslash. |
23ebc481 DM |
189 | */ |
190 | public function get_value() { | |
5f66d46e EL |
191 | |
192 | // Is the select multiple? | |
193 | $multiple = $this->field->hasAttribute('multiple'); | |
194 | ||
195 | $selectedoptions = array(); // To accumulate found selected options. | |
196 | ||
197 | // Selenium getValue() implementation breaks - separates - values having | |
198 | // commas within them, so we'll be looking for options with the 'selected' attribute instead. | |
199 | if ($this->running_javascript()) { | |
200 | // Get all the options in the select and extract their value/text pairs. | |
201 | $alloptions = $this->field->findAll('xpath', '//option'); | |
202 | foreach ($alloptions as $option) { | |
203 | // Is it selected? | |
204 | if ($option->hasAttribute('selected')) { | |
205 | if ($multiple) { | |
206 | // If the select is multiple, text commas must be encoded. | |
207 | $selectedoptions[] = trim(str_replace(',', '\,', $option->getText())); | |
208 | } else { | |
209 | $selectedoptions[] = trim($option->getText()); | |
210 | } | |
211 | } | |
212 | } | |
213 | ||
214 | // Goutte does not keep the 'selected' attribute updated, but its getValue() returns | |
215 | // the selected elements correctly, also those having commas within them. | |
216 | } else { | |
217 | $values = $this->field->getValue(); | |
218 | // Get all the options in the select and extract their value/text pairs. | |
219 | $alloptions = $this->field->findAll('xpath', '//option'); | |
220 | foreach ($alloptions as $option) { | |
221 | // Is it selected? | |
222 | if (in_array($option->getValue(), $values)) { | |
223 | if ($multiple) { | |
224 | // If the select is multiple, text commas must be encoded. | |
225 | $selectedoptions[] = trim(str_replace(',', '\,', $option->getText())); | |
226 | } else { | |
227 | $selectedoptions[] = trim($option->getText()); | |
228 | } | |
229 | } | |
230 | } | |
231 | } | |
232 | ||
233 | return implode(', ', $selectedoptions); | |
23ebc481 DM |
234 | } |
235 | } |