Passed
Pull Request — master (#21)
by
unknown
15:53
created

PantherDriver::sendKeys()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 3
dl 0
loc 6
ccs 4
cts 4
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
    /** @var array */
56 10
    private $kernelOptions;
57
    /** @var array */
58
    private $managerOptions;
59 10
60 10
    public function __construct(
61
        array $options = [],
62
        array $kernelOptions = [],
63
        array $managerOptions = []
64
    ) {
65
        $this->options = $options;
66
        $this->kernelOptions = $kernelOptions;
67 152
        $this->managerOptions = $managerOptions;
68
    }
69 152
70 1
    /**
71
     * Returns BrowserKit HTTP client instance.
72
     *
73 152
     * @return Client
74
     */
75
    public function getClient()
76
    {
77
        if (!$this->isStarted()) {
78
            throw new DriverException('Client is not (yet) started.');
79
        }
80
81
        return $this->client;
82
    }
83
84
    /**
85
     * Tells driver to remove hostname from URL.
86
     *
87
     * @param Boolean $remove
88
     *
89
     * @deprecated Deprecated as of 1.2, to be removed in 2.0. Pass the base url in the constructor instead.
90
     */
91
    public function setRemoveHostFromUrl($remove = true)
92
    {
93
        @trigger_error(
94
            'setRemoveHostFromUrl() is deprecated as of 1.2 and will be removed in 2.0. Pass the base url in the constructor instead.',
95
            E_USER_DEPRECATED
96
        );
97
        $this->removeHostFromUrl = (bool) $remove;
98
    }
99
100
    /**
101
     * Tells driver to remove script name from URL.
102
     *
103
     * @param Boolean $remove
104
     *
105
     * @deprecated Deprecated as of 1.2, to be removed in 2.0. Pass the base url in the constructor instead.
106
     */
107
    public function setRemoveScriptFromUrl($remove = true)
108
    {
109
        @trigger_error(
110
            'setRemoveScriptFromUrl() is deprecated as of 1.2 and will be removed in 2.0. Pass the base url in the constructor instead.',
111 3
            E_USER_DEPRECATED
112
        );
113 3
        $this->removeScriptFromUrl = (bool) $remove;
114 3
    }
115
116 3
    /**
117 3
     * {@inheritdoc}
118
     */
119
    public function start()
120
    {
121
        $this->client = self::createPantherClient($this->options, $this->kernelOptions, $this->managerOptions);
122 152
        $this->client->start();
123
124 152
        $this->started = true;
125
    }
126
127
    /**
128
     * {@inheritdoc}
129
     */
130 2
    public function isStarted()
131
    {
132 2
        return $this->started;
133 2
    }
134 2
135 2
    /**
136
     * {@inheritdoc}
137
     */
138
    public function stop()
139
    {
140 151
        $this->getClient()->quit();
141
        self::stopWebServer();
142
        $this->started = false;
143
    }
144 151
145 151
    /**
146 151
     * {@inheritdoc}
147
     */
148 151
    public function reset()
149
    {
150
        // experimental
151
        // $useSpeedUp = false;
152 151
        $useSpeedUp = true;
153
        if ($useSpeedUp) {
0 ignored issues
show
introduced by
The condition $useSpeedUp is always true.
Loading history...
154
            $this->getClient()->getWebDriver()->manage()->deleteAllCookies();
155
            try {
156 151
                $history = $this->getClient()->getHistory();
157 151
                if ($history) {
158
                    $history->clear();
159 151
                }
160
            } catch (\LogicException $e) {
161
                // History is not available when using e.g. WebDriver.
162
            }
163
            if (
164
                $this->getClient()->getWebDriver() instanceof JavaScriptExecutor
165
                && !in_array($this->getClient()->getCurrentURL(), ['', 'about:blank', 'data:,'], true)
166
            ) {
167
                $this->executeScript('localStorage.clear();');
168
            }
169
            // not sure if we should also close all windows
170
            // $lastWindowHandle = \end($this->getClient()->getWindowHandles());
171
            // if ($lastWindowHandle) {
172
            //     $this->getClient()->switchTo()->window($lastWindowHandle);
173
            // }
174
            // $this->getClient()->getWebDriver()->navigate()->refresh();
175
            // $this->getClient()->refreshCrawler();
176 151
            // if (\count($this->getClient()->getWindowHandles()) > 1) {
177
            //     $this->getClient()->getWebDriver()->close();
178
            // }
179
        } else {
180
            // Restarting the client resets the cookies and the history
181 141
            $this->getClient()->restart();
182
        }
183 141
184 141
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 7
    public function visit($url)
190
    {
191 7
        $this->getClient()->get($this->prepareUrl($url));
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 3
    public function getCurrentUrl()
198
    {
199 3
        return $this->getClient()->getCurrentURL();
200 3
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205 1
    public function reload()
206
    {
207 1
        $this->getClient()->reload();
208 1
    }
209
210
    /**
211
     * {@inheritdoc}
212
     */
213 1
    public function forward()
214
    {
215 1
        $this->getClient()->forward();
216 1
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 1
    public function back()
222
    {
223 1
        $this->getClient()->back();
224 1
    }
225 1
226
    /**
227
     * {@inheritdoc}
228
     */
229
    public function switchToWindow($name = null)
230 1
    {
231
        $this->getClient()->switchTo()->window($name);
232 1
        $this->getClient()->refreshCrawler();
233 1
    }
234 1
235 1
    /**
236 1
     * {@inheritdoc}
237
     */
238
    public function switchToIFrame($name = null)
239
    {
240 1
        if (null === $name) {
241 1
            $this->getClient()->switchTo()->defaultContent();
242
        } elseif ($name) {
243
            $iFrameElement = $this->getCrawlerElement($this->getFilteredCrawler(\sprintf("//iframe[@name='%s']", $name)));
244
            $this->getClient()->switchTo()->frame($iFrameElement);
245
        } else {
246 4
            $this->getClient()->switchTo()->frame(null);
247
        }
248 4
        $this->getClient()->refreshCrawler();
249 2
    }
250
251 2
    /**
252
     * {@inheritdoc}
253
     */
254 3
    public function setCookie($name, $value = null)
255
    {
256 3
        if (null === $value) {
257 3
            $this->deleteCookie($name);
258
259
            return;
260
        }
261
262 3
        $jar = $this->getClient()->getCookieJar();
263
        // @see: https://github.com/w3c/webdriver/issues/1238
264 3
        $jar->set(new Cookie($name, \rawurlencode((string) $value)));
265
    }
266 3
267 3
    /**
268 3
     * {@inheritdoc}
269
     */
270
    public function getCookie($name)
271
    {
272 1
        $cookies = $this->getClient()->getCookieJar()->all();
273
274
        foreach ($cookies as $cookie) {
275
            if ($cookie->getName() === $name) {
276
                return \urldecode($cookie->getValue());
277
            }
278 14
        }
279
280 14
        return null;
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286 1
    public function getContent()
287
    {
288 1
        return $this->getClient()->getWebDriver()->getPageSource();
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294 1
    public function getScreenshot($saveAs = null): string
295
    {
296 1
        return $this->getClient()->takeScreenshot($saveAs);
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302 1
    public function getWindowNames()
303
    {
304 1
        return $this->getClient()->getWindowHandles();
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     */
310 2
    public function getWindowName()
311
    {
312 2
        return $this->getClient()->getWindowHandle();
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318 6
    public function isVisible($xpath)
319
    {
320 6
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->isDisplayed();
321 5
    }
322
323
    /**
324
     * {@inheritdoc}.
325
     */
326 2
    public function mouseOver($xpath)
327
    {
328 2
        $this->getClient()->getMouse()->mouseMove($this->toCoordinates($xpath));
329 2
    }
330 2
331 1
    /**
332
     * {@inheritdoc}
333
     */
334
    public function focus($xpath)
335
    {
336 2
        $jsNode = $this->getJsNode($xpath);
337
        $script = \sprintf('%s.focus()', $jsNode);
338 2
        $this->executeScript($script);
339 2
    }
340
341 2
    /**
342 2
     * {@inheritdoc}
343
     */
344 2
    public function blur($xpath)
345 1
    {
346
        $jsNode = $this->getJsNode($xpath);
347
        $script = \sprintf('%s.blur();', $jsNode);
348
        // ensure element had active state; just for passing EventsTest::testBlur
349
        if ($this->evaluateScript(\sprintf('document.activeElement !== %s', $jsNode))) {
350 6
            $script = \sprintf('%s.focus();%s', $jsNode, $script);
351
        }
352 6
        $this->executeScript($script);
353 6
    }
354 5
355
    /**
356 5
     * {@inheritdoc}
357
     */
358 5
    public function keyPress($xpath, $char, $modifier = null)
359 4
    {
360 4
        $webDriverActions = $this->getWebDriverActions();
361 4
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
362
        $key = $this->geWebDriverKeyValue($char);
363 1
364
        $modifier = $this->getWebdriverModifierKeyValue($modifier);
365 5
366
        if ($modifier) {
367
            $webDriverActions->keyDown($element, $modifier)->perform();
368
            $webDriverActions->sendKeys($element, $key)->perform();
369
            $webDriverActions->keyUp($element, $modifier)->perform();
370 6
        } else {
371
            $webDriverActions->sendKeys($element, $key)->perform();
372 6
        }
373 6
    }
374 5
375
    public function sendKeys($xpath, $keys, $modifier = null)
0 ignored issues
show
Unused Code introduced by
The parameter $modifier is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

375
    public function sendKeys($xpath, $keys, /** @scrutinizer ignore-unused */ $modifier = null)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
376 5
    {
377 5
        $webDriverActions = $this->getWebDriverActions();
378 5
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
379 4
380
        $webDriverActions->sendKeys($element, $keys)->perform();
381 5
    }
382
383
    /**
384
     * {@inheritdoc}
385
     */
386 6
    public function keyDown($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
        $webDriverActions->keyDown($element, $key)->perform();
393 5
        $modifier = $this->getWebdriverModifierKeyValue($modifier);
394 4
        if ($modifier) {
395
            $webDriverActions->keyDown($element, $modifier)->perform();
396
        }
397 5
    }
398 5
399 5
    /**
400 4
     * {@inheritdoc}
401
     */
402 5
    public function keyUp($xpath, $char, $modifier = null)
403
    {
404
        $webDriverActions = $this->getWebDriverActions();
405
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
406
        $key = $this->geWebDriverKeyValue($char);
407 3
408
        $modifier = $this->getWebdriverModifierKeyValue($modifier);
409 3
        if ($modifier) {
410
            $webDriverActions->keyDown($element, $modifier)->perform();
411
        }
412
413
        $webDriverActions->keyDown($element, $key)->perform();
414
        $webDriverActions->keyUp($element, $key)->perform();
415 96
        if ($modifier) {
416
            $webDriverActions->keyUp($element, $modifier)->perform();
417 96
        }
418
    }
419 96
420 96
    /**
421 96
     * {@inheritdoc}
422
     */
423
    public function isSelected($xpath)
424 96
    {
425
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->isSelected();
426
    }
427
428
    /**
429
     * {@inheritdoc}
430 11
     */
431
    public function findElementXpaths($xpath)
432 11
    {
433
        $nodes = $this->getCrawler()->filterXPath($xpath);
434
435
        $elements = [];
436
        foreach ($nodes as $i => $node) {
437
            $elements[] = sprintf('(%s)[%d]', $xpath, $i + 1);
438 58
        }
439
440 58
        return $elements;
441 57
    }
442 57
443
    /**
444 57
     * {@inheritdoc}
445
     */
446
    public function getTagName($xpath)
447
    {
448
        return $this->getCrawlerElement($this->getFilteredCrawler($xpath))->getTagName();
449
    }
450 5
451
    /**
452
     * {@inheritdoc}
453 5
     */
454
    public function getText($xpath)
455
    {
456
        $text = $this->getFilteredCrawler($xpath)->text();
457
        $text = str_replace("\n", ' ', $text);
458
        $text = preg_replace('/ {2,}/', ' ', $text);
459 7
460
        return trim($text);
461 7
    }
462
463 5
    /**
464
     * {@inheritdoc}
465
     */
466
    public function getHtml($xpath)
467
    {
468
        // cut the tag itself (making innerHTML out of outerHTML)
469 10
        return preg_replace('/^\<[^\>]+\>|\<[^\>]+\>$/', '', $this->getOuterHtml($xpath));
470
    }
471 10
472
    /**
473 9
     * {@inheritdoc}
474
     */
475
    public function getOuterHtml($xpath)
476 9
    {
477 3
        $crawler = $this->getFilteredCrawler($xpath);
478 3
479 3
        return $crawler->html();
480 1
    }
481
482
    /**
483
     * {@inheritdoc}
484 9
     */
485
    public function getAttribute($xpath, $name)
486
    {
487
        $crawler = $this->getFilteredCrawler($xpath);
488
489
        $attribute = $this->getCrawlerElement($crawler)->getAttribute($name);
490 22
491
        // let's get hacky
492
        if ('' === $attribute) {
493 22
            $html = \strtolower($crawler->html());
494 17
            $name = \strtolower($name).'=';
495 17
            if (0 === \substr_count($html, $name)) {
496 17
                $attribute = null;
497
            }
498 7
        }
499
500 7
        return $attribute;
501 6
    }
502
503
    /**
504 21
     * {@inheritdoc}
505
     */
506
    public function getValue($xpath)
507
    {
508
        try {
509
            $formField = $this->getFormField($xpath);
510 32
            $value = $formField->getValue();
511
            if ('' === $value && $formField instanceof ChoiceFormField) {
512 32
                $value = null;
513 31
            }
514
        } catch (DriverException $e) {
515 31
            // e.g. element is an option
516 1
            $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
517
            $value = $element->getAttribute('value');
518
        }
519 31
520 31
        return $value;
521
    }
522
523
    /**
524
     * {@inheritdoc}
525
     */
526
    public function setValue($xpath, $value)
527
    {
528
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
529
        $jsNode = $this->getJsNode($xpath);
530
531
        if ('input' === $element->getTagName() && \in_array($element->getAttribute('type'), ['date', 'time', 'color'])) {
532
            $this->executeScript(\sprintf('%s.value = \'%s\'', $jsNode, $value));
533
        } else {
534 31
            try {
535 30
                $formField = $this->getFormField($xpath);
536
                $formField->setValue($value);
537 31
            } catch (DriverException $e) {
538
                // e.g. element is on option
539
                $element->sendKeys($value);
540
            }
541
        }
542 5
543
        // Remove the focus from the element if the field still has focus in
544 5
        // order to trigger the change event. By doing this instead of simply
545 3
        // triggering the change event for the given xpath we ensure that the
546
        // change event will not be triggered twice for the same element if it
547
        // has lost focus in the meanwhile. If the element has lost focus
548
        // already then there is nothing to do as this will already have caused
549
        // the triggering of the change event for that element.
550 5
        if ($this->evaluateScript(\sprintf('document.activeElement === %s', $jsNode))) {
551
            $this->executeScript('document.activeElement.blur();');
552 5
        }
553 3
    }
554
555
    /**
556
     * {@inheritdoc}
557
     */
558 11
    public function check($xpath)
559
    {
560 11
        $this->getChoiceFormField($xpath)->tick();
561
    }
562 10
563 1
    /**
564 1
     * {@inheritdoc}
565 1
     */
566 1
    public function uncheck($xpath)
567
    {
568
        $this->getChoiceFormField($xpath)->untick();
569
    }
570
571 9
    /**
572 9
     * {@inheritdoc}
573
     */
574
    public function selectOption($xpath, $value, $multiple = false)
575
    {
576
        $field = $this->getFormField($xpath);
577 33
578
        if (!$field instanceof ChoiceFormField) {
579 33
            throw new DriverException(
580 32
                sprintf(
581 32
                    'Impossible to select an option on the element with XPath "%s" as it is not a select or radio input',
582
                    $xpath
583
                )
584
            );
585
        }
586 3
587
        $field->select($value);
588 3
    }
589 2
590
    /**
591
     * {@inheritdoc}
592
     */
593
    public function click($xpath)
594 3
    {
595
        $this->getClient()->getMouse()->click($this->toCoordinates($xpath));
596 3
        $this->getClient()->refreshCrawler();
597 2
    }
598
599
    /**
600
     * {@inheritdoc}
601
     */
602 4
    public function doubleClick($xpath)
603
    {
604 4
        $this->getClient()->getMouse()->doubleClick($this->toCoordinates($xpath));
605
    }
606
607
    /**
608
     * {@inheritdoc}
609
     */
610 3
    public function rightClick($xpath)
611
    {
612 3
        $this->getClient()->getMouse()->contextClick($this->toCoordinates($xpath));
613
    }
614 2
615 1
    /**
616 1
     * {@inheritdoc}
617
     */
618
    public function isChecked($xpath)
619
    {
620 1
        return $this->getChoiceFormField($xpath)->hasValue();
621 1
    }
622
623
    /**
624
     * {@inheritdoc}
625
     */
626 1
    public function attachFile($xpath, $path)
627
    {
628 1
        $field = $this->getFormField($xpath);
629 1
630 1
        if (!$field instanceof FileFormField) {
631 1
            throw new DriverException(
632 1
                sprintf('Impossible to attach a file on the element with XPath "%s" as it is not a file input', $xpath)
633
            );
634
        }
635
636
        $field->upload($path);
637 151
    }
638
639 151
    /**
640 2
     * {@inheritdoc}
641 2
     */
642
    public function dragTo($sourceXpath, $destinationXpath)
643
    {
644 151
        $webDriverActions = $this->getWebDriverActions();
645
        $source = $this->getCrawlerElement($this->getFilteredCrawler($sourceXpath));
646
        $target = $this->getCrawlerElement($this->getFilteredCrawler($destinationXpath));
647
        $webDriverActions->dragAndDrop($source, $target)->perform();
648
    }
649
650 53
    /**
651
     * {@inheritdoc}
652 53
     */
653 39
    public function executeScript($script)
654
    {
655
        if (\preg_match('/^function[\s\(]/', $script)) {
656 53
            $script = \preg_replace('/;$/', '', $script);
657
            $script = '('.$script.')';
658
        }
659
660
        return $this->getClient()->executeScript($script);
661
    }
662 22
663
    /**
664 22
     * {@inheritdoc}
665 22
     */
666 22
    public function evaluateScript($script)
667
    {
668
        if (0 !== \strpos(\trim($script), 'return ')) {
669 22
            $script = 'return '.$script;
670 22
        }
671 22
672
        return $this->getClient()->executeScript($script);
673 22
    }
674
675
    /**
676
     * {@inheritdoc}
677
     */
678
    public function wait($timeout, $condition)
679 2
    {
680
        $script = "return $condition;";
681 2
        $start = microtime(true);
682 2
        $end = $start + $timeout / 1000.0;
683 2
684
        do {
685
            $result = $this->evaluateScript($script);
686
            \usleep(100000);
687
        } while (\microtime(true) < $end && !$result);
688 1
689
        return (bool) $result;
690 1
    }
691 1
692 1
    /**
693 1
     * {@inheritdoc}
694
     */
695
    public function resizeWindow($width, $height, $name = null)
696
    {
697
        $size = new WebDriverDimension($width, $height);
698 4
        $this->getClient()->getWebDriver()->manage()->window()->setSize($size);
699
    }
700 4
701
    /**
702 3
     * {@inheritdoc}
703 2
     */
704 2
    public function maximizeWindow($name = null)
705
    {
706
        $width = $this->evaluateScript('screen.width');
707
        $height = $this->evaluateScript('screen.height');
708
        $this->resizeWindow($width, $height, $name);
709
    }
710
711
    /**
712
     * {@inheritdoc}
713
     */
714
    public function submitForm($xpath)
715
    {
716
        $crawler = $this->getFilteredCrawler($xpath);
717
718
        $this->getClient()->submit($crawler->form());
719
        $this->getClient()->refreshCrawler();
720
    }
721
722
    /**
723
     * @return Response
724
     *
725
     * @throws DriverException If there is not response yet
726
     */
727
    protected function getResponse()
728
    {
729
        $response = $this->getClient()->getInternalResponse();
730 141
731
        if (null === $response) {
732 141
            throw new DriverException('Unable to access the response before visiting a page');
733
        }
734 141
735
        return $response;
736
    }
737
738
    /**
739
     * Prepares URL for visiting.
740
     * Removes "*.php/" from urls and then passes it to BrowserKitDriver::visit().
741
     *
742 2
     * @param string $url
743
     *
744 2
     * @return string
745 2
     */
746
    protected function prepareUrl($url)
747
    {
748 2
        $replacement = ($this->removeHostFromUrl ? '' : '$1').($this->removeScriptFromUrl ? '' : '$2');
749 2
750
        return preg_replace('#(https?\://[^/]+)(/[^/\.]+\.php)?#', $replacement, $url);
751
    }
752 2
753 2
    /**
754 2
     * Deletes a cookie by name.
755
     *
756
     * @param string $name Cookie name.
757
     */
758
    private function deleteCookie($name)
759
    {
760
        $path = $this->getCookiePath();
761 2
        $jar = $this->getClient()->getCookieJar();
762
763 2
        do {
764
            if (null !== $jar->get($name, $path)) {
765 2
                $jar->expire($name, $path);
766
            }
767
768
            $path = preg_replace('/.$/', '', $path);
769 2
        } while ($path);
770
    }
771
772
    /**
773
     * Returns current cookie path.
774
     *
775
     * @return string
776
     */
777
    private function getCookiePath()
778
    {
779
        $path = dirname(parse_url($this->getCurrentUrl(), PHP_URL_PATH));
780
781 50
        if ('\\' === DIRECTORY_SEPARATOR) {
782
            $path = str_replace('\\', '/', $path);
783
        }
784 50
785 38
        return $path;
786 38
    }
787
788 50
    /**
789
     * Returns form field from XPath query.
790 38
     *
791 11
     * @param string $xpath
792 11
     *
793
     * @return FormField
794
     *
795 50
     * @throws DriverException
796
     */
797 11
    private function getFormField($xpath)
798 10
    {
799 10
        try {
800
            $formField = $this->getChoiceFormField($xpath);
801
        } catch (DriverException $e) {
802 50
            $formField = null;
803 10
        }
804
        if (!$formField) {
805
            try {
806 47
                $formField = $this->getInputFormField($xpath);
807
            } catch (DriverException $e) {
808
                $formField = null;
809
            }
810
        }
811
        if (!$formField) {
812
            try {
813
                $formField = $this->getFileFormField($xpath);
814
            } catch (DriverException $e) {
815
                $formField = null;
816
            }
817
        }
818 57
        if (!$formField) {
819
            $formField = $this->getTextareaFormField($xpath);
820 57
        }
821
822 51
        return $formField;
823 37
    }
824 37
825 37
    /**
826 37
     * Returns the checkbox field from xpath query, ensuring it is valid.
827 37
     *
828 37
     * @param string $xpath
829
     *
830
     * @return ChoiceFormField
831
     *
832
     * @throws DriverException when the field is not a checkbox
833 20
     */
834
    private function getChoiceFormField($xpath)
835
    {
836
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
837
        try {
838
            $choiceFormField = new ChoiceFormField($element);
839
        } catch (\LogicException $e) {
840
            throw new DriverException(
841
                sprintf(
842
                    'Impossible to get the element with XPath "%s" as it is not a choice form field. %s',
843
                    $xpath,
844
                    $e->getMessage()
845 38
                )
846
            );
847 38
        }
848
849 35
        return $choiceFormField;
850 8
    }
851 8
852 8
    /**
853
     * Returns the input field from xpath query, ensuring it is valid.
854
     *
855
     * @param string $xpath
856 28
     *
857
     * @return InputFormField
858
     *
859
     * @throws DriverException when the field is not a checkbox
860
     */
861
    private function getInputFormField($xpath)
862
    {
863
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
864
        try {
865
            $inputFormField = new InputFormField($element);
866
        } catch (\LogicException $e) {
867
            throw new DriverException(
868 11
                sprintf('Impossible to check the element with XPath "%s" as it is not an input form field.', $xpath)
869
            );
870 11
        }
871
872 8
        return $inputFormField;
873 7
    }
874 7
875 7
    /**
876
     * Returns the input field from xpath query, ensuring it is valid.
877
     *
878
     * @param string $xpath
879 2
     *
880
     * @return FileFormField
881
     *
882
     * @throws DriverException when the field is not a checkbox
883
     */
884
    private function getFileFormField($xpath)
885
    {
886
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
887
        try {
888
            $fileFormField = new FileFormField($element);
889
        } catch (\LogicException $e) {
890
            throw new DriverException(
891 10
                sprintf('Impossible to check the element with XPath "%s" as it is not a file form field.', $xpath)
892
            );
893 10
        }
894
895 7
        return $fileFormField;
896 6
    }
897 6
898 6
    /**
899
     * Returns the textarea field from xpath query, ensuring it is valid.
900
     *
901
     * @param string $xpath
902 2
     *
903
     * @return TextareaFormField
904
     *
905
     * @throws DriverException when the field is not a checkbox
906
     */
907
    private function getTextareaFormField($xpath)
908
    {
909
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
910
        try {
911
            $textareaFormField = new TextareaFormField($element);
912
        } catch (\LogicException $e) {
913
            throw new DriverException(
914 83
                sprintf('Impossible to check the element with XPath "%s" as it is not a textarea.', $xpath)
915
            );
916 83
        }
917
918 83
        return $textareaFormField;
919 83
    }
920
921
    /**
922
     * Returns WebDriverElement from crawler instance.
923
     *
924
     * @param Crawler $crawler
925
     *
926
     * @return WebDriverElement
927
     *
928
     * @throws DriverException when the node does not exist
929
     */
930
    private function getCrawlerElement(Crawler $crawler): WebDriverElement
931
    {
932
        $node = $crawler->getElement(0);
933
934 121
        if (null !== $node) {
935
            return $node;
936 121
        }
937 22
938
        throw new DriverException('The element does not exist');
939
    }
940 99
941
    /**
942
     * Returns a crawler filtered for the given XPath, requiring at least 1 result.
943
     *
944
     * @param string $xpath
945
     *
946
     * @return Crawler
947
     *
948
     * @throws DriverException when no matching elements are found
949
     */
950 123
    private function getFilteredCrawler($xpath): Crawler
951
    {
952 123
        if (!count($crawler = $this->getCrawler()->filterXPath($xpath))) {
953
            throw new DriverException(sprintf('There is no element matching XPath "%s"', $xpath));
954 123
        }
955
956
        return $crawler;
957
    }
958 123
959
    /**
960
     * Returns crawler instance (got from client).
961 35
     *
962
     * @return Crawler
963 35
     *
964 35
     * @throws DriverException
965 35
     */
966
    private function getCrawler(): Crawler
967
    {
968
        $crawler = $this->getClient()->getCrawler();
969 42
970
        if (null === $crawler) {
971 42
            throw new DriverException('Unable to access the response content before visiting a page');
972
        }
973 38
974
        return $crawler;
975
    }
976
977
    private function getJsNode(string $xpath): string
978
    {
979 38
        return sprintf(
980
            'document.evaluate(`%s`, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue',
981
            $xpath
982 9
        );
983
    }
984 9
985 9
    private function toCoordinates(string $xpath): WebDriverCoordinates
986
    {
987
        $element = $this->getCrawlerElement($this->getFilteredCrawler($xpath));
988 9
989
        if (!$element instanceof WebDriverLocatable) {
990 9
            throw new \RuntimeException(
991
                sprintf('The element of "%s" xpath selector does not implement "%s".', $xpath, WebDriverLocatable::class)
992
            );
993 5
        }
994
995 5
        return $element->getCoordinates();
996 5
    }
997
998
    private function getWebDriverActions(): WebDriverActions
999 5
    {
1000
        $webDriver = $this->getClient()->getWebDriver();
1001
        if (!$webDriver instanceof WebDriverHasInputDevices) {
1002 5
            throw new UnsupportedDriverActionException('Mouse manipulations are not supported by %s', $this);
1003
        }
1004
        $webDriverActions = new WebDriverActions($webDriver);
1005 5
1006 1
        return $webDriverActions;
1007 1
    }
1008 4
1009 1
    private function geWebDriverKeyValue($char)
1010 1
    {
1011 3
        if (\is_int($char)) {
1012 1
            $char = \strtolower(\chr($char));
1013 1
        }
1014 2
1015 1
        return $char;
1016 1
    }
1017 1
1018 1
    private function getWebdriverModifierKeyValue(string $modifier = null): ?string
1019
    {
1020
        switch ($modifier) {
1021
            case 'alt':
1022
                $modifier = WebDriverKeys::ALT;
1023 5
                break;
1024
            case 'ctrl':
1025
                $modifier = WebDriverKeys::CONTROL;
1026
                break;
1027
            case 'shift':
1028
                $modifier = WebDriverKeys::SHIFT;
1029
                break;
1030
            case 'meta':
1031
                $modifier = WebDriverKeys::META;
1032
                break;
1033
            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...
1034
                break;
1035
            default:
1036
                throw new DriverException(\sprintf('Unsupported modifier "%s" given.', $modifier));
1037
        }
1038
1039
        return $modifier;
1040
    }
1041
}
1042