PantherDriver   F
last analyzed

Complexity

Total Complexity 132

Size/Duplication

Total Lines 996
Duplicated Lines 0 %

Test Coverage

Coverage 92.11%

Importance

Changes 25
Bugs 0 Features 5
Metric Value
eloc 315
c 25
b 0
f 5
dl 0
loc 996
ccs 350
cts 380
cp 0.9211
rs 2
wmc 132

69 Methods

Rating   Name   Duplication   Size   Complexity  
A switchToWindow() 0 3 1
A getHtml() 0 4 1
A check() 0 3 1
A getCrawlerElement() 0 9 2
A getJsNode() 0 5 1
A keyUp() 0 13 3
A setCookie() 0 11 2
A keyDown() 0 13 3
A getOuterHtml() 0 17 5
A click() 0 3 1
A isSelected() 0 3 1
A resizeWindow() 0 4 1
A getWebdriverModifierKeyValue() 0 22 6
A getInputFormField() 0 12 2
A isChecked() 0 3 1
A prepareUrl() 0 5 3
A getResponse() 0 9 2
A stop() 0 5 1
A findElementXpaths() 0 11 2
A getTextareaFormField() 0 12 2
A getFileFormField() 0 12 2
A getWindowName() 0 3 1
A isStarted() 0 3 1
A selectOption() 0 14 2
A getCurrentUrl() 0 3 1
A visit() 0 3 1
A mouseOver() 0 3 1
A reload() 0 3 1
A toCoordinates() 0 11 2
A dragTo() 0 6 1
A deleteCookie() 0 12 3
A maximizeWindow() 0 5 1
A getValue() 0 15 4
A getAttribute() 0 5 1
A getCrawler() 0 9 2
A focus() 0 5 1
A doubleClick() 0 3 1
A geWebDriverKeyValue() 0 7 2
A getCookiePath() 0 12 3
A getWindowNames() 0 3 1
A getText() 0 8 1
A getWebDriverActions() 0 9 2
B getFormField() 0 26 7
A getScreenshot() 0 3 1
A submitForm() 0 5 1
A attachFile() 0 11 2
A reset() 0 34 6
A executeScript() 0 8 2
A rightClick() 0 3 1
A start() 0 6 1
A switchToIFrame() 0 11 3
A uncheck() 0 3 1
A back() 0 3 1
A getContent() 0 3 1
A getChoiceFormField() 0 16 2
A isVisible() 0 3 1
A evaluateScript() 0 7 2
A getClient() 0 7 2
A keyPress() 0 14 2
A wait() 0 12 3
A getCookie() 0 11 3
A setRemoveHostFromUrl() 0 7 1
A setValue() 0 26 5
A blur() 0 9 2
A setRemoveScriptFromUrl() 0 7 1
A getTagName() 0 3 1
A __construct() 0 8 1
A getFilteredCrawler() 0 7 2
A forward() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like PantherDriver often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PantherDriver, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
/*
5
 * This file is part of the Behat\Mink.
6
 * (c) Robert Freigang <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Behat\Mink\Driver;
13
14
use Behat\Mink\Exception\DriverException;
15
use Behat\Mink\Exception\UnsupportedDriverActionException;
16
use Facebook\WebDriver\Exception\UnsupportedOperationException;
17
use Facebook\WebDriver\Interactions\Internal\WebDriverCoordinates;
18
use Facebook\WebDriver\Interactions\WebDriverActions;
19
use Facebook\WebDriver\Internal\WebDriverLocatable;
20
use Facebook\WebDriver\JavaScriptExecutor;
21
use Facebook\WebDriver\Remote\RemoteWebDriver;
22
use Facebook\WebDriver\Remote\RemoteWebElement;
23
use Facebook\WebDriver\WebDriverDimension;
24
use Facebook\WebDriver\WebDriverElement;
25
use Facebook\WebDriver\WebDriverHasInputDevices;
26
use Facebook\WebDriver\WebDriverKeys;
27
use Symfony\Component\BrowserKit\Cookie;
28
use Symfony\Component\BrowserKit\Response;
29
use Symfony\Component\DomCrawler\Field\FormField;
30
use Symfony\Component\Panther\Client;
31
use Symfony\Component\Panther\DomCrawler\Crawler;
32
use Symfony\Component\Panther\DomCrawler\Field\ChoiceFormField;
33
use Symfony\Component\Panther\DomCrawler\Field\FileFormField;
34
use Symfony\Component\Panther\DomCrawler\Field\InputFormField;
35
use Symfony\Component\Panther\DomCrawler\Field\TextareaFormField;
36
use Symfony\Component\Panther\PantherTestCaseTrait;
37
38
/**
39
 * Symfony2 Panther driver.
40
 *
41
 * @author Robert Freigang <[email protected]>
42
 */
43
class PantherDriver extends CoreDriver
44
{
45
    use PantherTestCaseTrait;
46
47
    // PantherTestCaseTrait needs this constants; provided via "\Symfony\Component\Panther\PantherTestCase"
48
    public const CHROME = 'chrome';
49
    public const FIREFOX = 'firefox';
50
51
    /** @var Client|null */
52
    private $client;
53
    private $started = false;
54
    private $removeScriptFromUrl = false;
55
    private $removeHostFromUrl = false;
56 10
    /** @var array */
57
    private $options;
58
    /** @var array */
59 10
    private $kernelOptions;
60 10
    /** @var array */
61
    private $managerOptions;
62
63
    public function __construct(
64
        array $options = [],
65
        array $kernelOptions = [],
66
        array $managerOptions = []
67 152
    ) {
68
        $this->options = $options;
69 152
        $this->kernelOptions = $kernelOptions;
70 1
        $this->managerOptions = $managerOptions;
71
    }
72
73 152
    /**
74
     * Returns BrowserKit HTTP client instance.
75
     *
76
     * @return Client
77
     */
78
    public function getClient()
79
    {
80
        if (!$this->isStarted()) {
81
            throw new DriverException('Client is not (yet) started.');
82
        }
83
84
        return $this->client;
85
    }
86
87
    /**
88
     * Tells driver to remove hostname from URL.
89
     *
90
     * @param Boolean $remove
91
     *
92
     * @deprecated Deprecated as of 1.2, to be removed in 2.0. Pass the base url in the constructor instead.
93
     */
94
    public function setRemoveHostFromUrl($remove = true)
95
    {
96
        @trigger_error(
97
            'setRemoveHostFromUrl() is deprecated as of 1.2 and will be removed in 2.0. Pass the base url in the constructor instead.',
98
            E_USER_DEPRECATED
99
        );
100
        $this->removeHostFromUrl = (bool) $remove;
101
    }
102
103
    /**
104
     * Tells driver to remove script name from URL.
105
     *
106
     * @param Boolean $remove
107
     *
108
     * @deprecated Deprecated as of 1.2, to be removed in 2.0. Pass the base url in the constructor instead.
109
     */
110
    public function setRemoveScriptFromUrl($remove = true)
111 3
    {
112
        @trigger_error(
113 3
            'setRemoveScriptFromUrl() is deprecated as of 1.2 and will be removed in 2.0. Pass the base url in the constructor instead.',
114 3
            E_USER_DEPRECATED
115
        );
116 3
        $this->removeScriptFromUrl = (bool) $remove;
117 3
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 152
    public function start()
123
    {
124 152
        $this->client = self::createPantherClient($this->options, $this->kernelOptions, $this->managerOptions);
125
        $this->client->start();
126
127
        $this->started = true;
128
    }
129
130 2
    /**
131
     * {@inheritdoc}
132 2
     */
133 2
    public function isStarted()
134 2
    {
135 2
        return $this->started;
136
    }
137
138
    /**
139
     * {@inheritdoc}
140 151
     */
141
    public function stop()
142
    {
143
        $this->getClient()->quit();
144 151
        self::stopWebServer();
145 151
        $this->started = false;
146 151
    }
147
148 151
    /**
149
     * {@inheritdoc}
150
     */
151
    public function reset()
152 151
    {
153
        // experimental
154
        // $useSpeedUp = false;
155
        $useSpeedUp = true;
156 151
        if ($useSpeedUp) {
0 ignored issues
show
introduced by
The condition $useSpeedUp is always true.
Loading history...
157 151
            $this->getClient()->getWebDriver()->manage()->deleteAllCookies();
158
            try {
159 151
                $history = $this->getClient()->getHistory();
160
                if ($history) {
161
                    $history->clear();
162
                }
163
            } catch (\LogicException $e) {
164
                // History is not available when using e.g. WebDriver.
165
            }
166
            if (
167
                $this->getClient()->getWebDriver() instanceof JavaScriptExecutor
168
                && !in_array($this->getClient()->getCurrentURL(), ['', 'about:blank', 'data:,'], true)
169
            ) {
170
                $this->executeScript('localStorage.clear();');
171
            }
172
            // not sure if we should also close all windows
173
            // $lastWindowHandle = \end($this->getClient()->getWindowHandles());
174
            // if ($lastWindowHandle) {
175
            //     $this->getClient()->switchTo()->window($lastWindowHandle);
176 151
            // }
177
            // $this->getClient()->getWebDriver()->navigate()->refresh();
178
            // $this->getClient()->refreshCrawler();
179
            // if (\count($this->getClient()->getWindowHandles()) > 1) {
180
            //     $this->getClient()->getWebDriver()->close();
181 141
            // }
182
        } else {
183 141
            // Restarting the client resets the cookies and the history
184 141
            $this->getClient()->restart();
185
        }
186
187
    }
188
189 7
    /**
190
     * {@inheritdoc}
191 7
     */
192
    public function visit($url)
193
    {
194
        $this->getClient()->get($this->prepareUrl($url));
195
    }
196
197 3
    /**
198
     * {@inheritdoc}
199 3
     */
200 3
    public function getCurrentUrl()
201
    {
202
        return $this->getClient()->getCurrentURL();
203
    }
204
205 1
    /**
206
     * {@inheritdoc}
207 1
     */
208 1
    public function reload()
209
    {
210
        $this->getClient()->reload();
211
    }
212
213 1
    /**
214
     * {@inheritdoc}
215 1
     */
216 1
    public function forward()
217
    {
218
        $this->getClient()->forward();
219
    }
220
221 1
    /**
222
     * {@inheritdoc}
223 1
     */
224 1
    public function back()
225 1
    {
226
        $this->getClient()->back();
227
    }
228
229
    /**
230 1
     * {@inheritdoc}
231
     */
232 1
    public function switchToWindow($name = null)
233 1
    {
234 1
        $this->getClient()->switchTo()->window($name);
235 1
    }
236 1
237
    /**
238
     * {@inheritdoc}
239
     */
240 1
    public function switchToIFrame($name = null)
241 1
    {
242
        if (null === $name) {
243
            $this->getClient()->switchTo()->defaultContent();
244
        } elseif ($name) {
245
            $iFrameElement = $this->getCrawlerElement($this->getFilteredCrawler(\sprintf("//iframe[@name='%s']", $name)));
246 4
            $this->getClient()->switchTo()->frame($iFrameElement);
247
        } else {
248 4
            $this->getClient()->switchTo()->frame(null);
249 2
        }
250
        $this->getClient()->refreshCrawler();
251 2
    }
252
253
    /**
254 3
     * {@inheritdoc}
255
     */
256 3
    public function setCookie($name, $value = null)
257 3
    {
258
        if (null === $value) {
259
            $this->deleteCookie($name);
260
261
            return;
262 3
        }
263
264 3
        $jar = $this->getClient()->getCookieJar();
265
        // @see: https://github.com/w3c/webdriver/issues/1238
266 3
        $jar->set(new Cookie($name, \rawurlencode((string) $value)));
267 3
    }
268 3
269
    /**
270
     * {@inheritdoc}
271
     */
272 1
    public function getCookie($name)
273
    {
274
        $cookies = $this->getClient()->getCookieJar()->all();
275
276
        foreach ($cookies as $cookie) {
277
            if ($cookie->getName() === $name) {
278 14
                return \urldecode($cookie->getValue());
279
            }
280 14
        }
281
282
        return null;
283
    }
284
285
    /**
286 1
     * {@inheritdoc}
287
     */
288 1
    public function getContent()
289
    {
290
        return $this->getClient()->getWebDriver()->getPageSource();
291
    }
292
293
    /**
294 1
     * {@inheritdoc}
295
     */
296 1
    public function getScreenshot($saveAs = null): string
297
    {
298
        return $this->getClient()->takeScreenshot($saveAs);
299
    }
300
301
    /**
302 1
     * {@inheritdoc}
303
     */
304 1
    public function getWindowNames()
305
    {
306
        return $this->getClient()->getWindowHandles();
307
    }
308
309
    /**
310 2
     * {@inheritdoc}
311
     */
312 2
    public function getWindowName()
313
    {
314
        return $this->getClient()->getWindowHandle();
315
    }
316
317
    /**
318 6
     * {@inheritdoc}
319
     */
320 6
    public function isVisible($xpath)
321 5
    {
322
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->isDisplayed();
323
    }
324
325
    /**
326 2
     * {@inheritdoc}.
327
     */
328 2
    public function mouseOver($xpath)
329 2
    {
330 2
        $this->getClient()->getMouse()->mouseMove($this->toCoordinates($xpath));
331 1
    }
332
333
    /**
334
     * {@inheritdoc}
335
     */
336 2
    public function focus($xpath)
337
    {
338 2
        $jsNode = $this->getJsNode($xpath);
339 2
        $script = \sprintf('%s.focus()', $jsNode);
340
        $this->executeScript($script);
341 2
    }
342 2
343
    /**
344 2
     * {@inheritdoc}
345 1
     */
346
    public function blur($xpath)
347
    {
348
        $jsNode = $this->getJsNode($xpath);
349
        $script = \sprintf('%s.blur();', $jsNode);
350 6
        // ensure element had active state; just for passing EventsTest::testBlur
351
        if ($this->evaluateScript(\sprintf('document.activeElement !== %s', $jsNode))) {
352 6
            $script = \sprintf('%s.focus();%s', $jsNode, $script);
353 6
        }
354 5
        $this->executeScript($script);
355
    }
356 5
357
    /**
358 5
     * {@inheritdoc}
359 4
     */
360 4
    public function keyPress($xpath, $char, $modifier = null)
361 4
    {
362
        $webDriverActions = $this->getWebDriverActions();
363 1
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
364
        $key = $this->geWebDriverKeyValue($char);
365 5
366
        $modifier = $this->getWebdriverModifierKeyValue($modifier);
367
368
        if ($modifier) {
369
            $webDriverActions->keyDown($element, $modifier)->perform();
370 6
            $webDriverActions->sendKeys($element, $key)->perform();
371
            $webDriverActions->keyUp($element, $modifier)->perform();
372 6
        } else {
373 6
            $webDriverActions->sendKeys($element, $key)->perform();
374 5
        }
375
    }
376 5
377 5
    /**
378 5
     * {@inheritdoc}
379 4
     */
380
    public function keyDown($xpath, $char, $modifier = null)
381 5
    {
382
        $webDriverActions = $this->getWebDriverActions();
383
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
384
        $key = $this->geWebDriverKeyValue($char);
385
386 6
        $modifier = $this->getWebdriverModifierKeyValue($modifier);
387
        if ($modifier) {
388 6
            $webDriverActions->keyDown($element, $modifier)->perform();
389 6
        }
390 5
        $webDriverActions->sendKeys($element, $key)->perform();
391
        if ($modifier) {
392 5
            $webDriverActions->keyUp($element, $modifier)->perform();
393 5
        }
394 4
    }
395
396
    /**
397 5
     * {@inheritdoc}
398 5
     */
399 5
    public function keyUp($xpath, $char, $modifier = null)
400 4
    {
401
        $webDriverActions = $this->getWebDriverActions();
402 5
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
403
        $key = $this->geWebDriverKeyValue($char);
404
405
        $modifier = $this->getWebdriverModifierKeyValue($modifier);
406
        if ($modifier) {
407 3
            $webDriverActions->keyDown($element, $modifier)->perform();
408
        }
409 3
        $webDriverActions->sendKeys($element, $key)->perform();
410
        if ($modifier) {
411
            $webDriverActions->keyUp($element, $modifier)->perform();
412
        }
413
    }
414
415 96
    /**
416
     * {@inheritdoc}
417 96
     */
418
    public function isSelected($xpath)
419 96
    {
420 96
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->isSelected();
421 96
    }
422
423
    /**
424 96
     * {@inheritdoc}
425
     */
426
    public function findElementXpaths($xpath)
427
    {
428
        $this->getClient()->refreshCrawler();
429
        $nodes = $this->getCrawler()->filterXPath($xpath);
430 11
431
        $elements = [];
432 11
        foreach ($nodes as $i => $node) {
433
            $elements[] = sprintf('(%s)[%d]', $xpath, $i + 1);
434
        }
435
436
        return $elements;
437
    }
438 58
439
    /**
440 58
     * {@inheritdoc}
441 57
     */
442 57
    public function getTagName($xpath)
443
    {
444 57
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->getTagName();
445
    }
446
447
    /**
448
     * {@inheritdoc}
449
     */
450 5
    public function getText($xpath)
451
    {
452
        $this->getClient()->refreshCrawler();
453 5
        $text = $this->getFilteredCrawler($xpath)->text();
454
        $text = str_replace("\n", ' ', $text);
455
        $text = preg_replace('/ {2,}/', ' ', $text);
456
457
        return trim($text);
458
    }
459 7
460
    /**
461 7
     * {@inheritdoc}
462
     */
463 5
    public function getHtml($xpath)
464
    {
465
        // cut the tag itself (making innerHTML out of outerHTML)
466
        return preg_replace('/^\<[^\>]+\>|\<[^\>]+\>$/', '', $this->getOuterHtml($xpath));
467
    }
468
469 10
    /**
470
     * {@inheritdoc}
471 10
     */
472
    public function getOuterHtml($xpath)
473 9
    {
474
        $crawler = $this->getFilteredCrawler($xpath);
475
476 9
        $crawlerElement = $this->getCrawlerElement($crawler);
477 3
        if ($crawlerElement instanceof RemoteWebElement) {
478 3
            $webDriver = $this->getClient()->getWebDriver();
479 3
            if ($webDriver instanceof RemoteWebDriver && $webDriver->isW3cCompliant()) {
480 1
                try {
481
                    return $crawlerElement->getDomProperty('outerHTML');
482
                } catch (UnsupportedOperationException $e) {
483
                    throw new DriverException($e->getMessage(), $e->getCode(), $e);
484 9
                }
485
            }
486
        }
487
488
        return $crawler->html();
489
    }
490 22
491
    /**
492
     * {@inheritdoc}
493 22
     */
494 17
    public function getAttribute($xpath, $name)
495 17
    {
496 17
        $crawler = $this->getFilteredCrawler($xpath);
497
498 7
        return $this->getCrawlerElement($crawler)->getAttribute($name);
499
    }
500 7
501 6
    /**
502
     * {@inheritdoc}
503
     */
504 21
    public function getValue($xpath)
505
    {
506
        try {
507
            $formField = $this->getFormField($xpath);
508
            $value = $formField->getValue();
509
            if ('' === $value && $formField instanceof ChoiceFormField) {
510 32
                $value = null;
511
            }
512 32
        } catch (DriverException $e) {
513 31
            // e.g. element is an option
514
            $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
515 31
            $value = $element->getAttribute('value');
516 1
        }
517
518
        return $value;
519 31
    }
520 31
521
    /**
522
     * {@inheritdoc}
523
     */
524
    public function setValue($xpath, $value)
525
    {
526
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
527
        $jsNode = $this->getJsNode($xpath);
528
529
        if ('input' === $element->getTagName() && \in_array($element->getAttribute('type'), ['date', 'time', 'color'])) {
530
            $this->executeScript(\sprintf('%s.value = \'%s\'', $jsNode, $value));
531
        } else {
532
            try {
533
                $formField = $this->getFormField($xpath);
534 31
                $formField->setValue($value);
535 30
            } catch (DriverException $e) {
536
                // e.g. element is on option
537 31
                $element->sendKeys($value);
538
            }
539
        }
540
541
        // Remove the focus from the element if the field still has focus in
542 5
        // order to trigger the change event. By doing this instead of simply
543
        // triggering the change event for the given xpath we ensure that the
544 5
        // change event will not be triggered twice for the same element if it
545 3
        // has lost focus in the meanwhile. If the element has lost focus
546
        // already then there is nothing to do as this will already have caused
547
        // the triggering of the change event for that element.
548
        if ($this->evaluateScript(\sprintf('document.activeElement === %s', $jsNode))) {
549
            $this->executeScript('document.activeElement.blur();');
550 5
        }
551
    }
552 5
553 3
    /**
554
     * {@inheritdoc}
555
     */
556
    public function check($xpath)
557
    {
558 11
        $this->getChoiceFormField($xpath)->tick();
559
    }
560 11
561
    /**
562 10
     * {@inheritdoc}
563 1
     */
564 1
    public function uncheck($xpath)
565 1
    {
566 1
        $this->getChoiceFormField($xpath)->untick();
567
    }
568
569
    /**
570
     * {@inheritdoc}
571 9
     */
572 9
    public function selectOption($xpath, $value, $multiple = false)
573
    {
574
        $field = $this->getFormField($xpath);
575
576
        if (!$field instanceof ChoiceFormField) {
577 33
            throw new DriverException(
578
                sprintf(
579 33
                    'Impossible to select an option on the element with XPath "%s" as it is not a select or radio input',
580 32
                    $xpath
581 32
                )
582
            );
583
        }
584
585
        $field->select($value);
586 3
    }
587
588 3
    /**
589 2
     * {@inheritdoc}
590
     */
591
    public function click($xpath)
592
    {
593
        $this->getClient()->getMouse()->click($this->toCoordinates($xpath));
594 3
    }
595
596 3
    /**
597 2
     * {@inheritdoc}
598
     */
599
    public function doubleClick($xpath)
600
    {
601
        $this->getClient()->getMouse()->doubleClick($this->toCoordinates($xpath));
602 4
    }
603
604 4
    /**
605
     * {@inheritdoc}
606
     */
607
    public function rightClick($xpath)
608
    {
609
        $this->getClient()->getMouse()->contextClick($this->toCoordinates($xpath));
610 3
    }
611
612 3
    /**
613
     * {@inheritdoc}
614 2
     */
615 1
    public function isChecked($xpath)
616 1
    {
617
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->isSelected();
618
    }
619
620 1
    /**
621 1
     * {@inheritdoc}
622
     */
623
    public function attachFile($xpath, $path)
624
    {
625
        $field = $this->getFormField($xpath);
626 1
627
        if (!$field instanceof FileFormField) {
628 1
            throw new DriverException(
629 1
                sprintf('Impossible to attach a file on the element with XPath "%s" as it is not a file input', $xpath)
630 1
            );
631 1
        }
632 1
633
        $field->upload($path);
634
    }
635
636
    /**
637 151
     * {@inheritdoc}
638
     */
639 151
    public function dragTo($sourceXpath, $destinationXpath)
640 2
    {
641 2
        $webDriverActions = $this->getWebDriverActions();
642
        $source = $this->getCrawlerElement($this->getFilteredCrawler($sourceXpath));
643
        $target = $this->getCrawlerElement($this->getFilteredCrawler($destinationXpath));
644 151
        $webDriverActions->dragAndDrop($source, $target)->perform();
645
    }
646
647
    /**
648
     * {@inheritdoc}
649
     */
650 53
    public function executeScript($script)
651
    {
652 53
        if (\preg_match('/^function[\s\(]/', $script)) {
653 39
            $script = \preg_replace('/;$/', '', $script);
654
            $script = '('.$script.')';
655
        }
656 53
657
        return $this->getClient()->executeScript($script);
658
    }
659
660
    /**
661
     * {@inheritdoc}
662 22
     */
663
    public function evaluateScript($script)
664 22
    {
665 22
        if (0 !== \strpos(\trim($script), 'return ')) {
666 22
            $script = 'return '.$script;
667
        }
668
669 22
        return $this->getClient()->executeScript($script);
670 22
    }
671 22
672
    /**
673 22
     * {@inheritdoc}
674
     */
675
    public function wait($timeout, $condition)
676
    {
677
        $script = "return $condition;";
678
        $start = microtime(true);
679 2
        $end = $start + $timeout / 1000.0;
680
681 2
        do {
682 2
            $result = $this->evaluateScript($script);
683 2
            \usleep(100000);
684
        } while (\microtime(true) < $end && !$result);
685
686
        return (bool) $result;
687
    }
688 1
689
    /**
690 1
     * {@inheritdoc}
691 1
     */
692 1
    public function resizeWindow($width, $height, $name = null)
693 1
    {
694
        $size = new WebDriverDimension($width, $height);
695
        $this->getClient()->getWebDriver()->manage()->window()->setSize($size);
696
    }
697
698 4
    /**
699
     * {@inheritdoc}
700 4
     */
701
    public function maximizeWindow($name = null)
702 3
    {
703 2
        $width = $this->evaluateScript('screen.width');
704 2
        $height = $this->evaluateScript('screen.height');
705
        $this->resizeWindow($width, $height, $name);
706
    }
707
708
    /**
709
     * {@inheritdoc}
710
     */
711
    public function submitForm($xpath)
712
    {
713
        $crawler = $this->getFilteredCrawler($xpath);
714
715
        $this->getClient()->submit($crawler->form());
716
    }
717
718
    /**
719
     * @return Response
720
     *
721
     * @throws DriverException If there is not response yet
722
     */
723
    protected function getResponse()
724
    {
725
        $response = $this->getClient()->getInternalResponse();
726
727
        if (null === $response) {
728
            throw new DriverException('Unable to access the response before visiting a page');
729
        }
730 141
731
        return $response;
732 141
    }
733
734 141
    /**
735
     * Prepares URL for visiting.
736
     * Removes "*.php/" from urls and then passes it to BrowserKitDriver::visit().
737
     *
738
     * @param string $url
739
     *
740
     * @return string
741
     */
742 2
    protected function prepareUrl($url)
743
    {
744 2
        $replacement = ($this->removeHostFromUrl ? '' : '$1').($this->removeScriptFromUrl ? '' : '$2');
745 2
746
        return preg_replace('#(https?\://[^/]+)(/[^/\.]+\.php)?#', $replacement, $url);
747
    }
748 2
749 2
    /**
750
     * Deletes a cookie by name.
751
     *
752 2
     * @param string $name Cookie name.
753 2
     */
754 2
    private function deleteCookie($name)
755
    {
756
        $path = $this->getCookiePath();
757
        $jar = $this->getClient()->getCookieJar();
758
759
        do {
760
            if (null !== $jar->get($name, $path)) {
761 2
                $jar->expire($name, $path);
762
            }
763 2
764
            $path = preg_replace('/.$/', '', $path);
765 2
        } while ($path);
766
    }
767
768
    /**
769 2
     * Returns current cookie path.
770
     *
771
     * @return string
772
     */
773
    private function getCookiePath()
774
    {
775
        $path = dirname(parse_url($this->getCurrentUrl(), PHP_URL_PATH));
776
777
        if ('\\' === DIRECTORY_SEPARATOR) {
778
            $path = str_replace('\\', '/', $path);
779
        }
780
        if (0 !== \substr_compare($path, '/', -1)) {
781 50
            $path .= '/';
782
        }
783
784 50
        return $path;
785 38
    }
786 38
787
    /**
788 50
     * Returns form field from XPath query.
789
     *
790 38
     * @param string $xpath
791 11
     *
792 11
     * @return FormField
793
     *
794
     * @throws DriverException
795 50
     */
796
    private function getFormField($xpath)
797 11
    {
798 10
        try {
799 10
            $formField = $this->getChoiceFormField($xpath);
800
        } catch (DriverException $e) {
801
            $formField = null;
802 50
        }
803 10
        if (!$formField) {
804
            try {
805
                $formField = $this->getInputFormField($xpath);
806 47
            } catch (DriverException $e) {
807
                $formField = null;
808
            }
809
        }
810
        if (!$formField) {
811
            try {
812
                $formField = $this->getFileFormField($xpath);
813
            } catch (DriverException $e) {
814
                $formField = null;
815
            }
816
        }
817
        if (!$formField) {
818 57
            $formField = $this->getTextareaFormField($xpath);
819
        }
820 57
821
        return $formField;
822 51
    }
823 37
824 37
    /**
825 37
     * Returns the checkbox field from xpath query, ensuring it is valid.
826 37
     *
827 37
     * @param string $xpath
828 37
     *
829
     * @return ChoiceFormField
830
     *
831
     * @throws DriverException when the field is not a checkbox
832
     */
833 20
    private function getChoiceFormField($xpath)
834
    {
835
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
836
        try {
837
            $choiceFormField = new ChoiceFormField($element);
838
        } catch (\LogicException $e) {
839
            throw new DriverException(
840
                sprintf(
841
                    'Impossible to get the element with XPath "%s" as it is not a choice form field. %s',
842
                    $xpath,
843
                    $e->getMessage()
844
                )
845 38
            );
846
        }
847 38
848
        return $choiceFormField;
849 35
    }
850 8
851 8
    /**
852 8
     * Returns the input field from xpath query, ensuring it is valid.
853
     *
854
     * @param string $xpath
855
     *
856 28
     * @return InputFormField
857
     *
858
     * @throws DriverException when the field is not a checkbox
859
     */
860
    private function getInputFormField($xpath)
861
    {
862
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
863
        try {
864
            $inputFormField = new InputFormField($element);
865
        } catch (\LogicException $e) {
866
            throw new DriverException(
867
                sprintf('Impossible to check the element with XPath "%s" as it is not an input form field.', $xpath)
868 11
            );
869
        }
870 11
871
        return $inputFormField;
872 8
    }
873 7
874 7
    /**
875 7
     * Returns the input field from xpath query, ensuring it is valid.
876
     *
877
     * @param string $xpath
878
     *
879 2
     * @return FileFormField
880
     *
881
     * @throws DriverException when the field is not a checkbox
882
     */
883
    private function getFileFormField($xpath)
884
    {
885
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
886
        try {
887
            $fileFormField = new FileFormField($element);
888
        } catch (\LogicException $e) {
889
            throw new DriverException(
890
                sprintf('Impossible to check the element with XPath "%s" as it is not a file form field.', $xpath)
891 10
            );
892
        }
893 10
894
        return $fileFormField;
895 7
    }
896 6
897 6
    /**
898 6
     * Returns the textarea field from xpath query, ensuring it is valid.
899
     *
900
     * @param string $xpath
901
     *
902 2
     * @return TextareaFormField
903
     *
904
     * @throws DriverException when the field is not a checkbox
905
     */
906
    private function getTextareaFormField($xpath)
907
    {
908
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
909
        try {
910
            $textareaFormField = new TextareaFormField($element);
911
        } catch (\LogicException $e) {
912
            throw new DriverException(
913
                sprintf('Impossible to check the element with XPath "%s" as it is not a textarea.', $xpath)
914 83
            );
915
        }
916 83
917
        return $textareaFormField;
918 83
    }
919 83
920
    /**
921
     * Returns WebDriverElement from crawler instance.
922
     *
923
     * @param Crawler $crawler
924
     *
925
     * @return WebDriverElement
926
     *
927
     * @throws DriverException when the node does not exist
928
     */
929
    private function getCrawlerElement(Crawler $crawler): WebDriverElement
930
    {
931
        $node = $crawler->getElement(0);
932
933
        if (null !== $node) {
934 121
            return $node;
935
        }
936 121
937 22
        throw new DriverException('The element does not exist');
938
    }
939
940 99
    /**
941
     * Returns a crawler filtered for the given XPath, requiring at least 1 result.
942
     *
943
     * @param string $xpath
944
     *
945
     * @return Crawler
946
     *
947
     * @throws DriverException when no matching elements are found
948
     */
949
    private function getFilteredCrawler($xpath): Crawler
950 123
    {
951
        if (!count($crawler = $this->getCrawler()->filterXPath($xpath))) {
952 123
            throw new DriverException(sprintf('There is no element matching XPath "%s"', $xpath));
953
        }
954 123
955
        return $crawler;
956
    }
957
958 123
    /**
959
     * Returns crawler instance (got from client).
960
     *
961 35
     * @return Crawler
962
     *
963 35
     * @throws DriverException
964 35
     */
965 35
    private function getCrawler(): Crawler
966
    {
967
        $crawler = $this->getClient()->getCrawler();
968
969 42
        if (null === $crawler) {
970
            throw new DriverException('Unable to access the response content before visiting a page');
971 42
        }
972
973 38
        return $crawler;
974
    }
975
976
    private function getJsNode(string $xpath): string
977
    {
978
        return sprintf(
979 38
            'document.evaluate(`%s`, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue',
980
            $xpath
981
        );
982 9
    }
983
984 9
    private function toCoordinates(string $xpath): WebDriverCoordinates
985 9
    {
986
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
987
988 9
        if (!$element instanceof WebDriverLocatable) {
989
            throw new \RuntimeException(
990 9
                sprintf('The element of "%s" xpath selector does not implement "%s".', $xpath, WebDriverLocatable::class)
991
            );
992
        }
993 5
994
        return $element->getCoordinates();
995 5
    }
996 5
997
    private function getWebDriverActions(): WebDriverActions
998
    {
999 5
        $webDriver = $this->getClient()->getWebDriver();
1000
        if (!$webDriver instanceof WebDriverHasInputDevices) {
1001
            throw new UnsupportedDriverActionException('Mouse manipulations are not supported by %s', $this);
1002 5
        }
1003
        $webDriverActions = new WebDriverActions($webDriver);
1004
1005 5
        return $webDriverActions;
1006 1
    }
1007 1
1008 4
    private function geWebDriverKeyValue($char)
1009 1
    {
1010 1
        if (\is_int($char)) {
1011 3
            $char = \strtolower(\chr($char));
1012 1
        }
1013 1
1014 2
        return $char;
1015 1
    }
1016 1
1017 1
    private function getWebdriverModifierKeyValue(string $modifier = null): ?string
1018 1
    {
1019
        switch ($modifier) {
1020
            case 'alt':
1021
                $modifier = WebDriverKeys::ALT;
1022
                break;
1023 5
            case 'ctrl':
1024
                $modifier = WebDriverKeys::CONTROL;
1025
                break;
1026
            case 'shift':
1027
                $modifier = WebDriverKeys::SHIFT;
1028
                break;
1029
            case 'meta':
1030
                $modifier = WebDriverKeys::META;
1031
                break;
1032
            case null:
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $modifier of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1033
                break;
1034
            default:
1035
                throw new DriverException(\sprintf('Unsupported modifier "%s" given.', $modifier));
1036
        }
1037
1038
        return $modifier;
1039
    }
1040
}
1041