Passed
Pull Request — master (#16)
by
unknown
03:00
created

PantherDriver::getWebDriverActions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0185

Importance

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