Completed
Push — master ( f105fc...3abe44 )
by Sergii
02:30
created

TqContext::pressElement()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
dl 0
loc 9
c 4
b 1
f 1
rs 9.6667
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
/**
3
 * @author Sergii Bondarenko, <[email protected]>
4
 */
5
namespace Drupal\TqExtension\Context;
6
7
// Helpers.
8
use Behat\Behat\Hook\Scope as BehatScope;
9
use Drupal\TqExtension\Utils\DatabaseManager;
10
11
class TqContext extends RawTqContext
12
{
13
    /**
14
     * The name and working element of main window.
15
     *
16
     * @var array
17
     */
18
    private $mainWindow = [];
19
    /**
20
     * @var DatabaseManager
21
     */
22
    private static $database;
23
24
    /**
25
     * Supports switching between the two windows only.
26
     *
27
     * @Given /^(?:|I )switch to opened window$/
28
     * @Then /^(?:|I )switch back to main window$/
29
     */
30
    public function iSwitchToWindow()
31
    {
32
        $windows = $this->getWindowNames();
33
34
        // If the window was not switched yet, then store it name and working element for future switching back.
35
        if (empty($this->mainWindow)) {
36
            $this->mainWindow['name'] = array_shift($windows);
37
            $this->mainWindow['element'] = $this->getWorkingElement();
38
39
            $window = reset($windows);
40
        } else {
41
            $window = $this->mainWindow['name'];
42
            $element = $this->mainWindow['element'];
43
44
            $this->mainWindow = [];
45
        }
46
47
        $this->getSession()->switchToWindow($window);
48
        $this->setWorkingElement(isset($element) ? $element : $this->getBodyElement());
49
    }
50
51
    /**
52
     * @Given /^(?:|I )switch to CKFinder window$/
53
     *
54
     * @javascript
55
     */
56
    public function switchToCKFinderWindow()
57
    {
58
        $this->iSwitchToWindow();
59
        $this->executeJsOnElement(
60
            $this->element('css', 'iframe'),
61
            "{{ELEMENT}}.setAttribute('id', 'behat_ckfinder');"
62
        );
63
        $this->iSwitchToAnIframe('behat_ckfinder');
64
    }
65
66
    /**
67
     * @param string $name
68
     *   An iframe name (null for switching back).
69
     *
70
     * @Given /^(?:|I )switch to an iframe "([^"]*)"$/
71
     * @Then /^(?:|I )switch back from an iframe$/
72
     */
73
    public function iSwitchToAnIframe($name = null)
74
    {
75
        $this->getSession()->switchToIFrame($name);
76
    }
77
78
    /**
79
     * Open the page with specified resolution.
80
     *
81
     * @param string $width_height
82
     *   String that satisfy the condition "<WIDTH>x<HEIGHT>".
83
     *
84
     * @example
85
     * Given I should use the "1280x800" resolution
86
     *
87
     * @Given /^(?:|I should )use the "([^"]*)" screen resolution$/
88
     */
89
    public function useScreenResolution($width_height)
90
    {
91
        list($width, $height) = explode('x', $width_height);
92
93
        $this->getSessionDriver()->resizeWindow((int) $width, (int) $height);
94
    }
95
96
    /**
97
     * @param string $action
98
     *   The next actions can be: "press", "click", "double click" and "right click".
99
     * @param string $selector
100
     *   CSS, inaccurate text or selector name from behat.yml can be used.
101
     *
102
     * @throws \WebDriver\Exception\NoSuchElement
103
     *   When element was not found.
104
     *
105
     * @Given /^(?:|I )((?:|(?:double|right) )click|press) on "([^"]*)"$/
106
     */
107
    public function pressElement($action, $selector)
108
    {
109
        $this->beforeStep();
110
        // 1. Get the action, divide string by spaces and put it parts into an array.
111
        // 2. Apply the "ucfirst" function for each array element.
112
        // 3. Make string from an array.
113
        // 4. Set the first letter of a string to lower case.
114
        $this->element('*', $selector)->{lcfirst(implode(array_map('ucfirst', explode(' ', $action))))}();
115
    }
116
117
    /**
118
     * @Given /^(?:|I )wait until AJAX is finished$/
119
     *
120
     * @javascript
121
     */
122
    public function waitUntilAjaxIsFinished()
123
    {
124
        $this->waitAjaxAndAnimations();
125
    }
126
127
    /**
128
     * @param string $selector
129
     *   CSS selector or region name.
130
     *
131
     * @Then /^(?:|I )work with elements in "([^"]*)"(?:| region)$/
132
     */
133
    public function workWithElementsInRegion($selector)
134
    {
135
        $this->setWorkingElement($this->element('css', $selector));
136
    }
137
138
    /**
139
     * @Then /^(?:|I )checkout to whole page$/
140
     */
141
    public function unsetWorkingElementScope()
142
    {
143
        $this->unsetWorkingElement();
144
    }
145
146
    /**
147
     * @param int $seconds
148
     *   Amount of seconds when nothing to happens.
149
     *
150
     * @Given /^(?:|I )wait (\d+) seconds$/
151
     */
152
    public function waitSeconds($seconds)
153
    {
154
        sleep($seconds);
155
    }
156
157
    /**
158
     * @param string $selector
159
     *   Text or CSS.
160
     *
161
     * @throws \Exception
162
     *
163
     * @Given /^(?:|I )scroll to "([^"]*)" element$/
164
     *
165
     * @javascript
166
     */
167
    public function scrollToElement($selector)
168
    {
169
        if (!self::hasTag('javascript')) {
170
            throw new \Exception('Scrolling to an element is impossible without a JavaScript.');
171
        }
172
173
        $this->executeJsOnElement($this->findElement($selector), '{{ELEMENT}}.scrollIntoView(true);');
0 ignored issues
show
Bug introduced by
It seems like $this->findElement($selector) can be null; however, executeJsOnElement() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
174
    }
175
176
    /**
177
     * IMPORTANT! The "BeforeSuite" hook should not be tagged, because suite has no tags!
178
     *
179
     * @BeforeSuite
180
     */
181
    public static function beforeSuite()
182
    {
183
        self::setDrupalVariables([
184
            // Set to "false", because the administration menu will not be rendered.
185
            // @see https://www.drupal.org/node/2023625#comment-8607207
186
            'admin_menu_cache_client' => false,
187
        ]);
188
    }
189
190
    /**
191
     * @param BehatScope\BeforeFeatureScope $scope
192
     *   Scope of the processing feature.
193
     *
194
     * @BeforeFeature
195
     */
196
    public static function beforeFeature(BehatScope\BeforeFeatureScope $scope)
197
    {
198
        self::collectTags($scope->getFeature()->getTags());
199
200
        // Database will be cloned for every feature with @cloneDB tag.
201
        if (self::hasTag('clonedb')) {
202
            self::$database = new DatabaseManager(self::getTag('clonedb', 'default'), self::class);
203
        }
204
    }
205
206
    /**
207
     * @AfterFeature
208
     */
209
    public static function afterFeature()
210
    {
211
        // Restore initial database when feature is done (call __destruct).
212
        self::$database = null;
213
    }
214
215
    /**
216
     * @param BehatScope\BeforeScenarioScope $scope
217
     *   Scope of the processing scenario.
218
     *
219
     * @BeforeScenario
220
     */
221
    public function beforeScenario(BehatScope\BeforeScenarioScope $scope)
222
    {
223
        self::collectTags($scope->getScenario()->getTags());
224
225
        // Any page should be visited due to using jQuery and checking the cookies.
226
        $this->visitPath('/');
227
        // By "Goutte" session we need to visit any page to be able to set a cookie
228
        // for this session and use it for checking request status codes.
229
        $this->visitPath('/', 'goutte');
230
    }
231
232
    /**
233
     * Set the jQuery handlers for "start" and "finish" events of AJAX queries.
234
     * In each method can be used the "waitAjaxAndAnimations" method for check
235
     * that AJAX was finished.
236
     *
237
     * @see RawTqContext::waitAjaxAndAnimations()
238
     *
239
     * @BeforeScenario @javascript
240
     */
241
    public function beforeScenarioJS()
242
    {
243
        $javascript = '';
244
245
        foreach (['Start' => 'true', 'Complete' => 'false'] as $event => $state) {
246
            $javascript .= "$(document).bind('ajax$event', function() {window.__behatAjax = $state;});";
247
        }
248
249
        $this->executeJs($javascript);
250
    }
251
252
    /**
253
     * IMPORTANT! The "BeforeStep" hook should not be tagged, because steps has no tags!
254
     *
255
     * @param BehatScope\BeforeStepScope $scope
256
     *   Scope of the processing step.
257
     *
258
     * @BeforeStep
259
     */
260
    public function beforeStep(BehatScope\BeforeStepScope $scope = null)
261
    {
262
        if (null !== $scope) {
263
            self::$pageUrl = $this->getCurrentUrl();
264
        }
265
    }
266
267
    /**
268
     * IMPORTANT! The "AfterStep" hook should not be tagged, because steps has no tags!
269
     *
270
     * @param BehatScope\AfterStepScope $scope
271
     *   Scope of the processing step.
272
     *
273
     * @AfterStep
274
     */
275
    public function afterStep(BehatScope\AfterStepScope $scope)
276
    {
277
        // If "mainWindow" variable is not empty that means that additional window has been opened.
278
        // Then, if number of opened windows equals to one, we need to switch back to original window,
279
        // otherwise an error will occur: "Window not found. The browser window may have been closed".
280
        // This happens due to auto closing window by JavaScript (CKFinder does this after choosing a file).
281
        if (!empty($this->mainWindow) && count($this->getWindowNames()) == 1) {
282
            $this->iSwitchToWindow();
283
        }
284
285
        if (self::hasTag('javascript') && self::isStepImpliesJsEvent($scope)) {
286
            $this->waitAjaxAndAnimations();
287
        }
288
    }
289
}
290