Passed
Push — master ( a84563...ace507 )
by Robert
01:46
created

PantherDriver::switchToWindow()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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