Passed
Push — master ( 8a5ccf...a84563 )
by Robert
02:27
created

PantherDriver::getWebdriverModifierKeyValue()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 22
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 6.0073

Importance

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