MDL-68353 test: get_field_locator to support custom field labels
[moodle.git] / lib / behat / form_field / behat_form_field.php
CommitLineData
23ebc481 1<?php
23ebc481
DM
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 * Generic moodleforms field.
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
28use Behat\Mink\Session as Session,
29 Behat\Mink\Element\NodeElement as NodeElement;
30
31/**
49d91129 32 * Representation of a form field.
23ebc481
DM
33 *
34 * Basically an interface with Mink session.
35 *
36 * @package core_form
37 * @category test
38 * @copyright 2012 David MonllaĆ³
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
40 */
41class behat_form_field {
42
46ac40cd
DM
43 /**
44 * @var Session Behat session.
45 */
23ebc481 46 protected $session;
46ac40cd
DM
47
48 /**
49 * @var NodeElement The field DOM node to interact with.
50 */
a4534dce 51 protected $field;
23ebc481 52
af4830a2
DM
53 /**
54 * @var string The field's locator.
55 */
56 protected $fieldlocator = false;
57
58
23ebc481
DM
59 /**
60 * General constructor with the node and the session to interact with.
61 *
62 * @param Session $session Reference to Mink session to traverse/modify the page DOM.
63 * @param NodeElement $fieldnode The field DOM node
64 * @return void
65 */
66 public function __construct(Session $session, NodeElement $fieldnode) {
67 $this->session = $session;
68 $this->field = $fieldnode;
69 }
70
71 /**
72 * Sets the value to a field.
73 *
74 * @param string $value
75 * @return void
76 */
77 public function set_value($value) {
8aff0eec
DM
78 // We delegate to the best guess, if we arrived here
79 // using the generic behat_form_field is because we are
80 // dealing with a fgroup element.
81 $instance = $this->guess_type();
82 return $instance->set_value($value);
23ebc481
DM
83 }
84
85 /**
86 * Returns the current value of the select element.
87 *
88 * @return string
89 */
90 public function get_value() {
8aff0eec
DM
91 // We delegate to the best guess, if we arrived here
92 // using the generic behat_form_field is because we are
93 // dealing with a fgroup element.
94 $instance = $this->guess_type();
95 return $instance->get_value();
7bc7625e
DM
96 }
97
0cf72524
MG
98 /**
99 * Presses specific keyboard key.
100 *
101 * @param mixed $char could be either char ('b') or char-code (98)
102 * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta')
103 */
104 public function key_press($char, $modifier = null) {
105 // We delegate to the best guess, if we arrived here
106 // using the generic behat_form_field is because we are
107 // dealing with a fgroup element.
108 $instance = $this->guess_type();
0db240c1 109 $instance->field->keyDown($char, $modifier);
cdc5f978
MG
110 try {
111 $instance->field->keyPress($char, $modifier);
112 $instance->field->keyUp($char, $modifier);
113 } catch (WebDriver\Exception $e) {
114 // If the JS handler attached to keydown or keypress destroys the element
115 // the later events may trigger errors because form element no longer exist
116 // or is not visible. Ignore such exceptions here.
f5459099
MN
117 } catch (\Behat\Mink\Exception\ElementNotFoundException $e) {
118 // Other Mink drivers can throw this for the same reason as above.
cdc5f978 119 }
0cf72524
MG
120 }
121
c3f1e953
DM
122 /**
123 * Generic match implementation
124 *
125 * Will work well with text-based fields, extension required
126 * for most of the other cases.
127 *
128 * @param string $expectedvalue
129 * @return bool The provided value matches the field value?
130 */
131 public function matches($expectedvalue) {
8aff0eec
DM
132 // We delegate to the best guess, if we arrived here
133 // using the generic behat_form_field is because we are
134 // dealing with a fgroup element.
135 $instance = $this->guess_type();
136 return $instance->matches($expectedvalue);
c3f1e953
DM
137 }
138
2159983a
DW
139 /**
140 * Get the value of an attribute set on this field.
141 *
142 * @param string $name The attribute name
143 * @return string The attribute value
144 */
145 public function get_attribute($name) {
146 return $this->field->getAttribute($name);
147 }
148
7bc7625e
DM
149 /**
150 * Guesses the element type we are dealing with in case is not a text-based element.
151 *
051e9663 152 * This class is the generic field type, behat_field_manager::get_form_field()
7bc7625e
DM
153 * should be able to find the appropiate class for the field type, but
154 * in cases like moodle form group elements we can not find the type of
155 * the field through the DOM so we also need to take care of the
156 * different field types from here. If we need to deal with more complex
157 * moodle form elements we will need to refactor this simple HTML elements
158 * guess method.
159 *
8aff0eec 160 * @return behat_form_field
7bc7625e
DM
161 */
162 private function guess_type() {
163 global $CFG;
164
8aff0eec
DM
165 // We default to the text-based field if nothing was detected.
166 if (!$type = behat_field_manager::guess_field_type($this->field, $this->session)) {
167 $type = 'text';
7bc7625e
DM
168 }
169
8aff0eec 170 $classname = 'behat_form_' . $type;
7bc7625e 171 $classpath = $CFG->dirroot . '/lib/behat/form_field/' . $classname . '.php';
060bafef 172 require_once($classpath);
7bc7625e 173 return new $classname($this->session, $this->field);
23ebc481
DM
174 }
175
28abad1a
DM
176 /**
177 * Returns whether the scenario is running in a browser that can run Javascript or not.
178 *
179 * @return bool
180 */
181 protected function running_javascript() {
182 return get_class($this->session->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver';
183 }
184
7bfb575f
AN
185 /**
186 * Waits for all the JS activity to be completed.
187 *
188 * @return bool Whether any JS is still pending completion.
189 */
190 protected function wait_for_pending_js() {
191 if (!$this->running_javascript()) {
192 // JS is not available therefore there is nothing to wait for.
193 return false;
194 }
195
196 return behat_base::wait_for_pending_js_in_session($this->session);
197 }
198
d1e55a47
DM
199 /**
200 * Gets the field internal id used by selenium wire protocol.
201 *
202 * Only available when running_javascript().
203 *
204 * @throws coding_exception
205 * @return int
206 */
207 protected function get_internal_field_id() {
208
209 if (!$this->running_javascript()) {
210 throw new coding_exception('You can only get an internal ID using the selenium driver.');
211 }
212
8aff0eec
DM
213 return $this->session->getDriver()->getWebDriverSession()->element('xpath', $this->field->getXPath())->getID();
214 }
215
216 /**
217 * Checks if the provided text matches the field value.
218 *
219 * @param string $expectedvalue
220 * @return bool
221 */
222 protected function text_matches($expectedvalue) {
223 if (trim($expectedvalue) != trim($this->get_value())) {
224 return false;
225 }
226 return true;
d1e55a47 227 }
af4830a2
DM
228
229 /**
230 * Gets the field locator.
231 *
232 * Defaults to the field label but you can
233 * specify other locators if you are interested.
234 *
235 * Public visibility as in most cases will be hard to
236 * use this method in a generic way, as fields can
237 * be selected using multiple ways (label, id, name...).
238 *
239 * @throws coding_exception
240 * @param string $locatortype
241 * @return string
242 */
243 protected function get_field_locator($locatortype = false) {
244
245 if (!empty($this->fieldlocator)) {
246 return $this->fieldlocator;
247 }
248
249 $fieldid = $this->field->getAttribute('id');
250
251 // Defaults to label.
252 if ($locatortype == 'label' || $locatortype == false) {
253
9b6efe3a 254 $labelnode = $this->session->getPage()->find('xpath', "//label[@for='$fieldid']|//p[@id='{$fieldid}_label']");
af4830a2
DM
255
256 // Exception only if $locatortype was specified.
257 if (!$labelnode && $locatortype == 'label') {
258 throw new coding_exception('Field with "' . $fieldid . '" id does not have a label.');
259 }
260
261 $this->fieldlocator = $labelnode->getText();
262 }
263
264 // Let's look for the name as a second option (more popular than
265 // id's when pointing to fields).
266 if (($locatortype == 'name' || $locatortype == false) &&
267 empty($this->fieldlocator)) {
268
269 $name = $this->field->getAttribute('name');
270
271 // Exception only if $locatortype was specified.
272 if (!$name && $locatortype == 'name') {
273 throw new coding_exception('Field with "' . $fieldid . '" id does not have a name attribute.');
274 }
275
276 $this->fieldlocator = $name;
277 }
278
279 // Otherwise returns the id if no specific locator type was provided.
280 if (empty($this->fieldlocator)) {
281 $this->fieldlocator = $fieldid;
282 }
283
284 return $this->fieldlocator;
285 }
23ebc481 286}