Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * 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
 
28
use Behat\Mink\Element\NodeElement;
29
use Behat\Mink\Session;
30
 
31
/**
32
 * Representation of a form field.
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
 */
41
class behat_form_field implements behat_session_interface {
42
 
43
    // All of the functionality of behat_base is shared with form fields via the behat_session_trait trait.
44
    use behat_session_trait;
45
 
46
    /**
47
     * @var Session Behat session.
48
     */
49
    protected $session;
50
 
51
    /**
52
     * @var NodeElement The field DOM node to interact with.
53
     */
54
    protected $field;
55
 
56
    /**
57
     * @var string The field's locator.
58
     */
59
    protected $fieldlocator = false;
60
 
61
    /**
62
     * Returns the Mink session.
63
     *
64
     * @param   string|null $name name of the session OR active session will be used
65
     * @return  \Behat\Mink\Session
66
     */
67
    public function getSession($name = null) {
68
        return $this->session;
69
    }
70
 
71
 
72
    /**
73
     * General constructor with the node and the session to interact with.
74
     *
75
     * @param Session $session Reference to Mink session to traverse/modify the page DOM.
76
     * @param NodeElement $fieldnode The field DOM node
77
     * @return void
78
     */
79
    public function __construct(Session $session, NodeElement $fieldnode) {
80
        $this->session = $session;
81
        $this->field = $fieldnode;
82
    }
83
 
84
    /**
85
     * Sets the value to a field.
86
     *
87
     * @param string $value
88
     * @return void
89
     */
90
    public function set_value($value) {
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->set_value($value);
96
    }
97
 
98
    /**
99
     * Returns the current value of the select element.
100
     *
101
     * @return string
102
     */
103
    public function get_value() {
104
        // We delegate to the best guess, if we arrived here
105
        // using the generic behat_form_field is because we are
106
        // dealing with a fgroup element.
107
        $instance = $this->guess_type();
108
        return $instance->get_value();
109
    }
110
 
111
    /**
112
     * Presses specific keyboard key.
113
     *
114
     * @param mixed  $char     could be either char ('b') or char-code (98)
115
     * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta')
116
     */
117
    public function key_press($char, $modifier = null) {
118
        // We delegate to the best guess, if we arrived here
119
        // using the generic behat_form_field is because we are
120
        // dealing with a fgroup element.
121
        $instance = $this->guess_type();
122
        $instance->field->keyDown($char, $modifier);
123
        try {
124
            $instance->field->keyPress($char, $modifier);
125
            $instance->field->keyUp($char, $modifier);
126
        } catch (\Facebook\WebDriver\Exception\WebDriverException $e) {
127
            // If the JS handler attached to keydown or keypress destroys the element
128
            // the later events may trigger errors because form element no longer exist
129
            // or is not visible. Ignore such exceptions here.
130
        } catch (\Behat\Mink\Exception\ElementNotFoundException $e) {
131
            // Other Mink drivers can throw this for the same reason as above.
132
        }
133
    }
134
 
135
    /**
136
     * Generic match implementation
137
     *
138
     * Will work well with text-based fields, extension required
139
     * for most of the other cases.
140
     *
141
     * @param string $expectedvalue
142
     * @return bool The provided value matches the field value?
143
     */
144
    public function matches($expectedvalue) {
145
        // We delegate to the best guess, if we arrived here
146
        // using the generic behat_form_field is because we are
147
        // dealing with a fgroup element.
148
        $instance = $this->guess_type();
149
        return $instance->matches($expectedvalue);
150
    }
151
 
152
    /**
153
     * Get the value of an attribute set on this field.
154
     *
155
     * @param string $name The attribute name
156
     * @return string The attribute value
157
     */
158
    public function get_attribute($name) {
159
        return $this->field->getAttribute($name);
160
    }
161
 
162
    /**
163
     * Guesses the element type we are dealing with in case is not a text-based element.
164
     *
165
     * This class is the generic field type, behat_field_manager::get_form_field()
166
     * should be able to find the appropiate class for the field type, but
167
     * in cases like moodle form group elements we can not find the type of
168
     * the field through the DOM so we also need to take care of the
169
     * different field types from here. If we need to deal with more complex
170
     * moodle form elements we will need to refactor this simple HTML elements
171
     * guess method.
172
     *
173
     * @return behat_form_field
174
     */
175
    private function guess_type() {
176
        return $this->get_field_instance_for_element($this->field);
177
    }
178
 
179
    /**
180
     * Returns the appropriate form field object for a given node element.
181
     *
182
     * @param NodeElement $element The node element
183
     * @return behat_form_field
184
     */
185
    protected function get_field_instance_for_element(NodeElement $element): behat_form_field {
186
        global $CFG;
187
 
188
        // We default to the text-based field if nothing was detected.
189
        if (!$type = behat_field_manager::guess_field_type($element, $this->session)) {
190
            $type = 'text';
191
        }
192
 
193
        $classname = 'behat_form_' . $type;
194
        $classpath = $CFG->dirroot . '/lib/behat/form_field/' . $classname . '.php';
195
        require_once($classpath);
196
 
197
        return new $classname($this->session, $element);
198
    }
199
 
200
    /**
201
     * Returns whether the scenario is running in a browser that can run Javascript or not.
202
     *
203
     * @return bool
204
     */
205
    protected function running_javascript() {
206
        return get_class($this->session->getDriver()) !== 'Behat\Mink\Driver\BrowserKitDriver';
207
    }
208
 
209
    /**
210
     * Waits for all the JS activity to be completed.
211
     *
212
     * @return bool Whether any JS is still pending completion.
213
     */
214
    protected function wait_for_pending_js() {
215
        if (!$this->running_javascript()) {
216
            // JS is not available therefore there is nothing to wait for.
217
            return false;
218
        }
219
 
220
        return behat_base::wait_for_pending_js_in_session($this->session);
221
    }
222
 
223
    /**
224
     * Gets the field internal id used by selenium wire protocol.
225
     *
226
     * Only available when running_javascript().
227
     *
228
     * @throws coding_exception
229
     * @return int
230
     */
231
    protected function get_internal_field_id() {
232
        if (!$this->running_javascript()) {
233
            throw new coding_exception('You can only get an internal ID using the selenium driver.');
234
        }
235
 
236
        return $this->getSession()
237
            ->getDriver()
238
            ->getWebDriver()
239
            ->findElement(WebDriverBy::xpath($node->getXpath()))
240
            ->getID();
241
    }
242
 
243
    /**
244
     * Checks if the provided text matches the field value.
245
     *
246
     * @param string $expectedvalue
247
     * @param string|null $actualvalue The actual value. If not specified, this will be fetched from $this->get_value().
248
     * @return bool
249
     */
250
    protected function text_matches($expectedvalue, ?string $actualvalue = null): bool {
251
        $actualvalue = $actualvalue ?? $this->get_value();
252
 
253
        // Non strict string comparison.
254
        if (trim($expectedvalue) == trim($actualvalue)) {
255
            return true;
256
        }
257
 
258
        // Do one more matching attempt for floats that are valid with current decsep in use
259
        // (let's continue non strict comparing them as strings, but once unformatted).
260
        $expectedfloat = unformat_float(trim($expectedvalue), true);
261
        $actualfloat = unformat_float(trim($actualvalue), true);
262
        // If they aren't null or false, then we are good to be compared (basically is_numeric()).
263
        $goodfloats = !is_null($expectedfloat) && ($expectedfloat !== false) &&
264
            !is_null($actualfloat) && ($actualfloat !== false);
265
        if ($goodfloats && ((string)$expectedfloat == (string)$actualfloat)) {
266
            return true;
267
        }
268
 
269
        return false;
270
    }
271
 
272
    /**
273
     * Gets the field locator.
274
     *
275
     * Defaults to the field label but you can
276
     * specify other locators if you are interested.
277
     *
278
     * Public visibility as in most cases will be hard to
279
     * use this method in a generic way, as fields can
280
     * be selected using multiple ways (label, id, name...).
281
     *
282
     * @throws coding_exception
283
     * @param string $locatortype
284
     * @return string
285
     */
286
    protected function get_field_locator($locatortype = false) {
287
 
288
        if (!empty($this->fieldlocator)) {
289
            return $this->fieldlocator;
290
        }
291
 
292
        $fieldid = $this->field->getAttribute('id');
293
 
294
        // Defaults to label.
295
        if ($locatortype == 'label' || $locatortype == false) {
296
 
297
            $labelnode = $this->session->getPage()->find('xpath', "//label[@for='$fieldid']|//p[@id='{$fieldid}_label']");
298
 
299
            // Exception only if $locatortype was specified.
300
            if (!$labelnode && $locatortype == 'label') {
301
                throw new coding_exception('Field with "' . $fieldid . '" id does not have a label.');
302
            }
303
 
304
            $this->fieldlocator = $labelnode->getText();
305
        }
306
 
307
        // Let's look for the name as a second option (more popular than
308
        // id's when pointing to fields).
309
        if (($locatortype == 'name' || $locatortype == false) &&
310
                empty($this->fieldlocator)) {
311
 
312
            $name = $this->field->getAttribute('name');
313
 
314
            // Exception only if $locatortype was specified.
315
            if (!$name && $locatortype == 'name') {
316
                throw new coding_exception('Field with "' . $fieldid . '" id does not have a name attribute.');
317
            }
318
 
319
            $this->fieldlocator = $name;
320
        }
321
 
322
        // Otherwise returns the id if no specific locator type was provided.
323
        if (empty($this->fieldlocator)) {
324
            $this->fieldlocator = $fieldid;
325
        }
326
 
327
        return $this->fieldlocator;
328
    }
329
}