Passed
Push — master ( 192a34...19d2b3 )
by Robert
02:25
created

PantherDriver::resizeWindow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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