Passed
Push — master ( f43ce6...97fdd7 )
by Robert
02:24
created

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