Passed
Push — master ( 0afa7e...f49bd4 )
by Robert
03:06
created

PantherDriver::check()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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