behat_form_field.php 8.94 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Generic moodleforms field.
 *
 * @package    core_form
 * @category   test
 * @copyright  2012 David Monllaó
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.

use Behat\Mink\Session as Session,
    Behat\Mink\Element\NodeElement as NodeElement;

/**
32
 * Representation of a form field.
33
34
35
36
37
38
39
40
41
42
 *
 * Basically an interface with Mink session.
 *
 * @package    core_form
 * @category   test
 * @copyright  2012 David Monllaó
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class behat_form_field {

43
44
45
    /**
     * @var Session Behat session.
     */
46
    protected $session;
47
48
49
50

    /**
     * @var NodeElement The field DOM node to interact with.
     */
51
    protected $field;
52

53
54
55
56
57
58
    /**
     * @var string The field's locator.
     */
    protected $fieldlocator = false;


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    /**
     * General constructor with the node and the session to interact with.
     *
     * @param Session $session Reference to Mink session to traverse/modify the page DOM.
     * @param NodeElement $fieldnode The field DOM node
     * @return void
     */
    public function __construct(Session $session, NodeElement $fieldnode) {
        $this->session = $session;
        $this->field = $fieldnode;
    }

    /**
     * Sets the value to a field.
     *
     * @param string $value
     * @return void
     */
    public function set_value($value) {
78
79
80
81
82
        // We delegate to the best guess, if we arrived here
        // using the generic behat_form_field is because we are
        // dealing with a fgroup element.
        $instance = $this->guess_type();
        return $instance->set_value($value);
83
84
85
86
87
88
89
90
    }

    /**
     * Returns the current value of the select element.
     *
     * @return string
     */
    public function get_value() {
91
92
93
94
95
        // We delegate to the best guess, if we arrived here
        // using the generic behat_form_field is because we are
        // dealing with a fgroup element.
        $instance = $this->guess_type();
        return $instance->get_value();
96
97
    }

98
99
100
101
102
103
104
105
106
107
108
    /**
     * Presses specific keyboard key.
     *
     * @param mixed  $char     could be either char ('b') or char-code (98)
     * @param string $modifier keyboard modifier (could be 'ctrl', 'alt', 'shift' or 'meta')
     */
    public function key_press($char, $modifier = null) {
        // We delegate to the best guess, if we arrived here
        // using the generic behat_form_field is because we are
        // dealing with a fgroup element.
        $instance = $this->guess_type();
109
        $instance->field->keyDown($char, $modifier);
110
111
112
113
114
115
116
        try {
            $instance->field->keyPress($char, $modifier);
            $instance->field->keyUp($char, $modifier);
        } catch (WebDriver\Exception $e) {
            // If the JS handler attached to keydown or keypress destroys the element
            // the later events may trigger errors because form element no longer exist
            // or is not visible. Ignore such exceptions here.
117
118
        } catch (\Behat\Mink\Exception\ElementNotFoundException $e) {
            // Other Mink drivers can throw this for the same reason as above.
119
        }
120
121
    }

122
123
124
125
126
127
128
129
130
131
    /**
     * Generic match implementation
     *
     * Will work well with text-based fields, extension required
     * for most of the other cases.
     *
     * @param string $expectedvalue
     * @return bool The provided value matches the field value?
     */
    public function matches($expectedvalue) {
132
133
134
135
136
        // We delegate to the best guess, if we arrived here
        // using the generic behat_form_field is because we are
        // dealing with a fgroup element.
        $instance = $this->guess_type();
        return $instance->matches($expectedvalue);
137
138
    }

139
140
141
    /**
     * Guesses the element type we are dealing with in case is not a text-based element.
     *
142
     * This class is the generic field type, behat_field_manager::get_form_field()
143
144
145
146
147
148
149
     * should be able to find the appropiate class for the field type, but
     * in cases like moodle form group elements we can not find the type of
     * the field through the DOM so we also need to take care of the
     * different field types from here. If we need to deal with more complex
     * moodle form elements we will need to refactor this simple HTML elements
     * guess method.
     *
150
     * @return behat_form_field
151
152
153
154
     */
    private function guess_type() {
        global $CFG;

155
156
157
        // We default to the text-based field if nothing was detected.
        if (!$type = behat_field_manager::guess_field_type($this->field, $this->session)) {
            $type = 'text';
158
159
        }

160
        $classname = 'behat_form_' . $type;
161
        $classpath = $CFG->dirroot . '/lib/behat/form_field/' . $classname . '.php';
162
        require_once($classpath);
163
        return new $classname($this->session, $this->field);
164
165
    }

166
167
168
169
170
171
172
173
174
    /**
     * Returns whether the scenario is running in a browser that can run Javascript or not.
     *
     * @return bool
     */
    protected function running_javascript() {
        return get_class($this->session->getDriver()) !== 'Behat\Mink\Driver\GoutteDriver';
    }

175
176
177
178
179
180
181
182
183
184
185
186
187
188
    /**
     * Waits for all the JS activity to be completed.
     *
     * @return bool Whether any JS is still pending completion.
     */
    protected function wait_for_pending_js() {
        if (!$this->running_javascript()) {
            // JS is not available therefore there is nothing to wait for.
            return false;
        }

        return behat_base::wait_for_pending_js_in_session($this->session);
    }

189
190
191
192
193
194
195
196
197
198
199
200
201
202
    /**
     * Gets the field internal id used by selenium wire protocol.
     *
     * Only available when running_javascript().
     *
     * @throws coding_exception
     * @return int
     */
    protected function get_internal_field_id() {

        if (!$this->running_javascript()) {
            throw new coding_exception('You can only get an internal ID using the selenium driver.');
        }

203
204
205
206
207
208
209
210
211
212
213
214
215
216
        return $this->session->getDriver()->getWebDriverSession()->element('xpath', $this->field->getXPath())->getID();
    }

    /**
     * Checks if the provided text matches the field value.
     *
     * @param string $expectedvalue
     * @return bool
     */
    protected function text_matches($expectedvalue) {
        if (trim($expectedvalue) != trim($this->get_value())) {
            return false;
        }
        return true;
217
    }
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275

    /**
     * Gets the field locator.
     *
     * Defaults to the field label but you can
     * specify other locators if you are interested.
     *
     * Public visibility as in most cases will be hard to
     * use this method in a generic way, as fields can
     * be selected using multiple ways (label, id, name...).
     *
     * @throws coding_exception
     * @param string $locatortype
     * @return string
     */
    protected function get_field_locator($locatortype = false) {

        if (!empty($this->fieldlocator)) {
            return $this->fieldlocator;
        }

        $fieldid = $this->field->getAttribute('id');

        // Defaults to label.
        if ($locatortype == 'label' || $locatortype == false) {

            $labelnode = $this->session->getPage()->find('xpath', '//label[@for="' . $fieldid . '"]');

            // Exception only if $locatortype was specified.
            if (!$labelnode && $locatortype == 'label') {
                throw new coding_exception('Field with "' . $fieldid . '" id does not have a label.');
            }

            $this->fieldlocator = $labelnode->getText();
        }

        // Let's look for the name as a second option (more popular than
        // id's when pointing to fields).
        if (($locatortype == 'name' || $locatortype == false) &&
                empty($this->fieldlocator)) {

            $name = $this->field->getAttribute('name');

            // Exception only if $locatortype was specified.
            if (!$name && $locatortype == 'name') {
                throw new coding_exception('Field with "' . $fieldid . '" id does not have a name attribute.');
            }

            $this->fieldlocator = $name;
        }

        // Otherwise returns the id if no specific locator type was provided.
        if (empty($this->fieldlocator)) {
            $this->fieldlocator = $fieldid;
        }

        return $this->fieldlocator;
    }
276
}