Passed
Push — master ( ace507...22ee12 )
by Robert
02:46 queued 01:00
created

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