RawTqContext::executeJsOnElement()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 14
ccs 0
cts 9
cp 0
rs 9.4285
cc 1
eloc 8
nc 1
nop 2
crap 2
1
<?php
2
/**
3
 * @author Sergii Bondarenko, <[email protected]>
4
 */
5
namespace Drupal\TqExtension\Context;
6
7
// Contexts.
8
use Behat\Behat\Context\SnippetAcceptingContext;
9
use Drupal\DrupalExtension\Context as DrupalContexts;
10
// Exceptions.
11
use Behat\Behat\Context\Exception\ContextNotFoundException;
12
use Behat\DebugExtension\Debugger;
13
// Helpers.
14
use WebDriver\Session;
15
use Drupal\Driver\DrushDriver;
16
use Behat\Mink\Element\NodeElement;
17
use Behat\Mink\Driver\Selenium2Driver;
18
use Behat\Behat\Hook\Scope\StepScope;
19
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
20
// Utils.
21
use Drupal\TqExtension\Utils\Tags;
22
23
/**
24
 * @see RawTqContext::__call()
25
 *
26
 * @method User\UserContext getUserContext()
27
 * @method Node\NodeContext getNodeContext()
28
 * @method Form\FormContext getFormContext()
29
 * @method Email\EmailContext getEmailContext()
30
 * @method Drush\DrushContext getDrushContext()
31
 * @method Wysiwyg\WysiwygContext getWysiwygContext()
32
 * @method Redirect\RedirectContext getRedirectContext()
33
 * @method TqContext getTqContext()
34
 * @method DrupalContexts\MinkContext getMinkContext()
35
 * @method DrupalContexts\DrupalContext getDrupalContext()
36
 * @method DrupalContexts\MessageContext getMessageContext()
37
 * @method \Drupal\Component\Utility\Random getRandom()
38
 */
39
class RawTqContext extends RawPageContext implements TqContextInterface
40
{
41
    use Debugger;
42
    use Tags;
43
44
    /**
45
     * Parameters of TqExtension.
46
     *
47
     * @var array
48
     */
49
    private $parameters = [];
50
    /**
51
     * @var string
52
     */
53
    protected static $pageUrl = '';
54
55
    /**
56
     * @param string $method
57
     * @param array $arguments
58
     *
59
     * @throws \Exception
60
     * @throws ContextNotFoundException
61
     *   When context class cannot be loaded.
62
     *
63
     * @return SnippetAcceptingContext
64
     */
65
    public function __call($method, array $arguments)
66
    {
67
        $environment = $this->getEnvironment();
68
        // @example
69
        // The "getFormContext" method is not declared and his name will be split by capital
70
        // letters, creating an array with three items: "get", "Form" and "Context".
71
        list(, $base, $context) = preg_split('/(?=[A-Z])/', $method);
72
73
        foreach ([
74
            [$this->getTqParameter('namespace'), 'Context', $base],
75
            ['Drupal', 'DrupalExtension', 'Context'],
76
        ] as $class) {
77
            $class[] = "$base$context";
78
            $class = implode('\\', $class);
79
80
            if ($environment->hasContextClass($class)) {
81
                return $environment->getContext($class);
82
            }
83
        }
84
85
        throw new \Exception(sprintf('Method %s does not exist', $method));
86
    }
87
88
    /**
89
     * @param array $variables
90
     *   An associative array where key is a variable name and a value - value.
91
     */
92
    public static function setDrupalVariables(array $variables)
93
    {
94
        foreach ($variables as $name => $value) {
95
            variable_set($name, $value);
96
        }
97
    }
98
99
    /**
100
     * Get selector by name.
101
     *
102
     * @param string $name
103
     *   Selector name from the configuration file.
104
     *
105
     * @return string
106
     *   CSS selector.
107
     *
108
     * @throws \Exception
109
     *   If selector does not exits.
110
     */
111
    public function getDrupalSelector($name)
112
    {
113
        $selectors = $this->getDrupalParameter('selectors');
114
115
        if (!isset($selectors[$name])) {
116
            throw new \Exception(sprintf('No such selector configured: %s', $name));
117
        }
118
119
        return $selectors[$name];
120
    }
121
122
    /**
123
     * {@inheritdoc}
124
     */
125
    public function getDrupalText($name)
126
    {
127
        // Make text selectors translatable.
128
        return t(parent::getDrupalText($name));
129
    }
130
131
    /**
132
     * @param string $site
133
     *   Drupal site folder.
134
     *
135
     * @return string
136
     *   URL to files directory.
137
     */
138
    public function getFilesUrl($site = 'default')
139
    {
140
        return $this->locatePath("sites/$site/files");
141
    }
142
143
    /**
144
     * @param string $text
145
     *   JS code for processing.
146
     *
147
     * @return self
148
     */
149
    protected function processJavaScript(&$text)
150
    {
151
        $text = str_replace(['$'], ['jQuery'], $text);
152
153
        return $this;
154
    }
155
156
    /**
157
     * @return InitializedContextEnvironment
158
     */
159
    public function getEnvironment()
160
    {
161
        return $this->getDrupal()->getEnvironment();
162
    }
163
164
    /**
165
     * @return Selenium2Driver
166
     */
167
    public function getSessionDriver()
168
    {
169
        return $this->getSession()->getDriver();
170
    }
171
172
    /**
173
     * @return Session
174
     */
175
    public function getWebDriverSession()
176
    {
177
        return $this->getSessionDriver()->getWebDriverSession();
178
    }
179
180
    /**
181
     * @todo Remove this when DrupalExtension will be used Mink >=1.6 and use $this->getSession->getWindowNames();
182
     *
183
     * @return string[]
184
     */
185
    public function getWindowNames()
186
    {
187
        return $this->getWebDriverSession()->window_handles();
188
    }
189
190
    /**
191
     * @param NodeElement $element
192
     * @param string $script
193
     *
194
     * @example
195
     * $this->executeJsOnElement($this->element('*', 'Meta tags'), 'return jQuery({{ELEMENT}}).text();');
196
     * $this->executeJsOnElement($this->element('*', '#menu'), '{{ELEMENT}}.focus();');
197
     *
198
     * @throws \Exception
199
     *
200
     * @return mixed
201
     */
202
    public function executeJsOnElement(NodeElement $element, $script)
203
    {
204
        $session = $this->getWebDriverSession();
205
        // We need to trigger something with "withSyn" method, because, otherwise an element won't be found.
206
        $element->focus();
207
208
        $this->processJavaScript($script);
209
        self::debug([$script]);
210
211
        return $session->execute([
212
            'script' => str_replace('{{ELEMENT}}', 'arguments[0]', $script),
213
            'args' => [['ELEMENT' => $session->element('xpath', $element->getXpath())->getID()]],
214
        ]);
215
    }
216
217
    /**
218
     * @param string $javascript
219
     *   JS code for execution.
220
     * @param array $args
221
     *   Placeholder declarations.
222
     *
223
     * @return mixed
224
     */
225
    public function executeJs($javascript, array $args = [])
226
    {
227
        $javascript = format_string($javascript, $args);
228
229
        $this->processJavaScript($javascript);
230
        self::debug([$javascript]);
231
232
        return $this->getSession()->evaluateScript($javascript);
233
    }
234
235
    /**
236
     * @param string $file
237
     *   Existing file from "src/JavaScript" without ".js" extension.
238
     * @param bool $delete
239
     *   Whether injection should be deleted.
240
     */
241
    protected static function injectCustomJavascript($file, $delete = false)
242
    {
243
        $file .= '.js';
244
        $modulePath = drupal_get_filename('module', 'system');
245
        $destination = dirname($modulePath) . '/' . $file;
246
        $injection = "\ndrupal_add_js('$destination', array('every_page' => TRUE));";
247
248
        if ($delete) {
249
            file_unmanaged_delete("$destination");
250
251
            $search = $injection;
252
            $replace = '';
253
        } else {
254
            file_unmanaged_copy(
255
                str_replace('Context', 'JavaScript', __DIR__) . '/' . $file,
256
                $destination,
257
                FILE_EXISTS_REPLACE
258
            );
259
260
            $search = 'system_add_module_assets();';
261
            $replace = $search . $injection;
262
        }
263
264
        file_put_contents($modulePath, str_replace($search, $replace, file_get_contents($modulePath)));
265
    }
266
267
    /**
268
     * Check JS events in step definition.
269
     *
270
     * @param StepScope $event
271
     *
272
     * @return int
273
     */
274
    public static function isStepImpliesJsEvent(StepScope $event)
275
    {
276
        return preg_match('/(follow|press|click|submit)/i', $event->getStep()->getText());
277
    }
278
279
    /**
280
     * @return DrushDriver
281
     */
282
    public function getDrushDriver()
283
    {
284
        return $this->getDriver('drush');
285
    }
286
287
    /**
288
     * Wait for all AJAX requests and jQuery animations.
289
     */
290
    public function waitAjaxAndAnimations()
291
    {
292
        $this->getSession()
293
             ->wait(1000, "window.__behatAjax === false && !jQuery(':animated').length && !jQuery.active");
294
    }
295
296
    /**
297
     * {@inheritdoc}
298
     */
299
    public function setTqParameters(array $parameters)
300
    {
301
        if (empty($this->parameters)) {
302
            $this->parameters = $parameters;
303
        }
304
    }
305
306
    /**
307
     * @param string $name
308
     *   The name of parameter from behat.yml.
309
     *
310
     * @return mixed
311
     */
312
    public function getTqParameter($name)
313
    {
314
        return isset($this->parameters[$name]) ? $this->parameters[$name] : false;
315
    }
316
317
    /**
318
     * {@inheritdoc}
319
     */
320
    public function locatePath($path = '')
321
    {
322
        // Obtain base URL when path is empty, or not starts from "//" or "http".
323
        if (empty($path) || strpos($path, '//') !== 0 && strpos($path, 'http') !== 0) {
324
            $path = rtrim($this->getMinkParameter('base_url'), '/') . '/' . ltrim($path, '/');
325
        }
326
327
        $url = parse_url($path);
328
329
        if (!isset($url['host'])) {
330
            throw new \InvalidArgumentException(sprintf('Incorrect URL: %s', func_get_arg(0)));
331
        }
332
333
        // When URL starts from "//" the "scheme" key will not exists.
334
        if (isset($url['scheme'])) {
335
            // Check scheme.
336
            if (!in_array($url['scheme'], ['http', 'https'])) {
337
                throw new \InvalidArgumentException(sprintf('%s is not valid scheme.', $url['scheme']));
338
            }
339
340
            $path = $url['scheme'] . ':';
341
        } else {
342
            // Process "//" at the start.
343
            $path = '';
344
        }
345
346
        $path .= '//';
347
348
        if (isset($url['user'], $url['pass'])) {
349
            // Encode special characters in username and password. Useful
350
            // when some item contain something like "@" symbol.
351
            foreach (['user' => ':', 'pass' => '@'] as $part => $suffix) {
352
                $path .= rawurlencode($url[$part]) . $suffix;
353
            }
354
        }
355
356
        $path .= $url['host'];
357
358
        // Append additional URL components.
359
        foreach (['port' => ':', 'path' => '', 'query' => '?', 'fragment' => '#'] as $part => $prefix) {
360
            if (isset($url[$part])) {
361
                $path .= $prefix . $url[$part];
362
            }
363
        }
364
365
        return $path;
366
    }
367
368
    /**
369
     * @return string
370
     */
371
    public function getCurrentUrl()
372
    {
373
        return $this->locatePath($this->getSession()->getCurrentUrl());
374
    }
375
}
376