1818use Facebook \WebDriver \Exception \TimeoutException ;
1919use Facebook \WebDriver \Exception \UnsupportedOperationException ;
2020use Facebook \WebDriver \Exception \WebDriverException ;
21+ use Facebook \WebDriver \Interactions \WebDriverActions ;
22+ use Facebook \WebDriver \Internal \WebDriverLocatable ;
23+ use Facebook \WebDriver \JavaScriptExecutor ;
2124use Facebook \WebDriver \Remote \DesiredCapabilities ;
2225use Facebook \WebDriver \Remote \RemoteWebDriver ;
23- use Facebook \WebDriver \Remote \RemoteWebElement ;
2426use Facebook \WebDriver \Remote \WebDriverBrowserType ;
2527use Facebook \WebDriver \Remote \WebDriverCapabilityType ;
28+ use Facebook \WebDriver \WebDriver ;
2629use Facebook \WebDriver \WebDriverBy ;
2730use Facebook \WebDriver \WebDriverDimension ;
2831use Facebook \WebDriver \WebDriverElement ;
32+ use Facebook \WebDriver \WebDriverHasInputDevices ;
2933use Facebook \WebDriver \WebDriverPlatform ;
3034use Facebook \WebDriver \WebDriverRadios ;
3135use Facebook \WebDriver \WebDriverSelect ;
3539 * @phpstan-type TTimeouts array{script?: null|numeric, implicit?: null|numeric, page?: null|numeric, "page load"?: null|numeric, pageLoad?: null|numeric}
3640 * @phpstan-type TCapabilities array<string, mixed>
3741 * @phpstan-type TElementValue array<array-key, mixed>|bool|mixed|string|null
38- * @phpstan-type TWebDriverInstantiator callable(string $driverHost, DesiredCapabilities $capabilities): RemoteWebDriver
42+ * @phpstan-type TWebDriver WebDriver&JavaScriptExecutor&WebDriverHasInputDevices
43+ * @phpstan-type TWebDriverElement WebDriverElement&WebDriverLocatable
44+ * @phpstan-type TWebDriverInstantiator callable(string $driverHost, DesiredCapabilities $capabilities): TWebDriver
3945 */
4046class WebdriverClassicDriver extends CoreDriver
4147{
@@ -77,7 +83,10 @@ class WebdriverClassicDriver extends CoreDriver
7783
7884 private const W3C_WINDOW_HANDLE_PREFIX = 'w3cwh: ' ;
7985
80- private ?RemoteWebDriver $ webDriver = null ;
86+ /**
87+ * @var TWebDriver|null
88+ */
89+ private ?WebDriver $ webDriver = null ;
8190
8291 private string $ browserName ;
8392
@@ -210,7 +219,7 @@ public function switchToWindow(?string $name = null): void
210219 public function switchToIFrame (?string $ name = null ): void
211220 {
212221 $ frameQuery = $ name ;
213- if ($ name && $ this ->getWebDriver ()-> isW3cCompliant ()) {
222+ if ($ name && $ this ->isW3cCompliant ()) {
214223 try {
215224 $ frameQuery = $ this ->getWebDriver ()->findElement (WebDriverBy::id ($ name ));
216225 } catch (NoSuchElementException $ e ) {
@@ -569,14 +578,18 @@ public function doubleClick(
569578 #[Language('XPath ' )]
570579 string $ xpath
571580 ): void {
572- $ this ->doubleClickOnElement ($ this ->findElement ($ xpath ));
581+ $ element = $ this ->findElement ($ xpath );
582+ $ element ->getLocationOnScreenOnceScrolledIntoView ();
583+ $ this ->actions ()->doubleClick ($ element )->perform ();
573584 }
574585
575586 public function rightClick (
576587 #[Language('XPath ' )]
577588 string $ xpath
578589 ): void {
579- $ this ->rightClickOnElement ($ this ->findElement ($ xpath ));
590+ $ element = $ this ->findElement ($ xpath );
591+ $ element ->getLocationOnScreenOnceScrolledIntoView ();
592+ $ this ->actions ()->contextClick ($ element )->perform ();
580593 }
581594
582595 public function attachFile (
@@ -601,7 +614,9 @@ public function mouseOver(
601614 #[Language('XPath ' )]
602615 string $ xpath
603616 ): void {
604- $ this ->mouseOverElement ($ this ->findElement ($ xpath ));
617+ $ element = $ this ->findElement ($ xpath );
618+ $ element ->getLocationOnScreenOnceScrolledIntoView ();
619+ $ this ->actions ()->moveToElement ($ element )->perform ();
605620 }
606621
607622 public function focus (
@@ -656,7 +671,7 @@ public function dragTo(
656671 ): void {
657672 $ source = $ this ->findElement ($ sourceXpath );
658673 $ destination = $ this ->findElement ($ destinationXpath );
659- $ this ->getWebDriver ()-> action ()->dragAndDrop ($ source , $ destination )->perform ();
674+ $ this ->actions ()->dragAndDrop ($ source , $ destination )->perform ();
660675 }
661676
662677 public function executeScript (
@@ -751,8 +766,8 @@ public function getBrowserName(): string
751766 */
752767 public function getWebDriverSessionId (): ?string
753768 {
754- return $ this ->isStarted ()
755- ? $ this ->getWebDriver ()->getSessionID ()
769+ return $ this ->isStarted () && method_exists ( $ this -> getWebDriver (), ' getSessionId ' )
770+ ? $ this ->getAsString ( $ this -> getWebDriver ()->getSessionID (), ' Session ID ' )
756771 : null ;
757772 }
758773
@@ -789,15 +804,16 @@ protected function createWebDriver(): void
789804 }
790805
791806 /**
807+ * @return TWebDriver
792808 * @throws DriverException
793809 */
794- protected function getWebDriver (): RemoteWebDriver
810+ protected function getWebDriver (): WebDriver
795811 {
796- if ($ this ->webDriver ) {
797- return $ this -> webDriver ;
812+ if (! $ this ->webDriver ) {
813+ throw new DriverException ( ' Base driver has not been created ' ) ;
798814 }
799815
800- throw new DriverException ( ' Base driver has not been created ' ) ;
816+ return $ this -> webDriver ;
801817 }
802818
803819 // </editor-fold>
@@ -886,6 +902,15 @@ private function createBrowserSpecificCapabilities(): DesiredCapabilities
886902 }
887903 }
888904
905+ /**
906+ * @throws DriverException
907+ */
908+ private function actions (): WebDriverActions
909+ {
910+ // WebDriverActions are not reset after being performed - that's why we create a new instance each time.
911+ return new WebDriverActions ($ this ->getWebDriver ());
912+ }
913+
889914 /**
890915 * @throws DriverException
891916 */
@@ -963,11 +988,12 @@ private function executeJsOnXpath(
963988 * $this->executeJsOnElement($element, 'return argument[0].childNodes.length');
964989 * ```
965990 *
991+ * @param TWebDriverElement $element
966992 * @return mixed
967993 * @throws DriverException
968994 */
969995 private function executeJsOnElement (
970- WebDriverElement $ element ,
996+ $ element ,
971997 #[Language('JavaScript ' )]
972998 string $ script
973999 ) {
@@ -1040,37 +1066,14 @@ private function getWindowHandleFromName(string $name): string
10401066 }
10411067 }
10421068
1043- private function clickOnElement (WebDriverElement $ element ): void
1044- {
1045- $ element ->getLocationOnScreenOnceScrolledIntoView ();
1046- $ element ->click ();
1047- }
1048-
10491069 /**
1070+ * @param TWebDriverElement $element
10501071 * @throws DriverException
10511072 */
1052- private function doubleClickOnElement ( RemoteWebElement $ element ): void
1073+ private function clickOnElement ( $ element ): void
10531074 {
10541075 $ element ->getLocationOnScreenOnceScrolledIntoView ();
1055- $ this ->getWebDriver ()->getMouse ()->doubleClick ($ element ->getCoordinates ());
1056- }
1057-
1058- /**
1059- * @throws DriverException
1060- */
1061- private function rightClickOnElement (RemoteWebElement $ element ): void
1062- {
1063- $ element ->getLocationOnScreenOnceScrolledIntoView ();
1064- $ this ->getWebDriver ()->getMouse ()->contextClick ($ element ->getCoordinates ());
1065- }
1066-
1067- /**
1068- * @throws DriverException
1069- */
1070- private function mouseOverElement (RemoteWebElement $ element ): void
1071- {
1072- $ element ->getLocationOnScreenOnceScrolledIntoView ();
1073- $ this ->getWebDriver ()->getMouse ()->mouseMove ($ element ->getCoordinates ());
1076+ $ this ->actions ()->click ($ element )->perform ();
10741077 }
10751078
10761079 /**
@@ -1100,24 +1103,28 @@ private function withWindow(?string $name, callable $callback): void
11001103 }
11011104
11021105 /**
1106+ * @return TWebDriverElement
11031107 * @throws DriverException
11041108 */
11051109 private function findElement (
11061110 #[Language('XPath ' )]
11071111 string $ xpath
1108- ): RemoteWebElement {
1112+ ) {
11091113 try {
11101114 $ finder = WebDriverBy::xpath ($ xpath );
1111- return $ this ->getWebDriver ()->findElement ($ finder );
1115+ $ element = $ this ->getWebDriver ()->findElement ($ finder );
1116+ assert ($ element instanceof WebDriverLocatable);
1117+ return $ element ;
11121118 } catch (\Throwable $ e ) {
11131119 throw new DriverException ("Failed to find element: {$ e ->getMessage ()}" , 0 , $ e );
11141120 }
11151121 }
11161122
11171123 /**
1124+ * @param TWebDriverElement $element
11181125 * @throws DriverException
11191126 */
1120- private function selectRadioValue (WebDriverElement $ element , string $ value ): void
1127+ private function selectRadioValue ($ element , string $ value ): void
11211128 {
11221129 try {
11231130 (new WebDriverRadios ($ element ))->selectByValue ($ value );
@@ -1133,9 +1140,10 @@ private function selectRadioValue(WebDriverElement $element, string $value): voi
11331140 }
11341141
11351142 /**
1143+ * @param TWebDriverElement $element
11361144 * @throws DriverException
11371145 */
1138- private function selectOptionOnElement (WebDriverElement $ element , string $ value , bool $ multiple = false ): void
1146+ private function selectOptionOnElement ($ element , string $ value , bool $ multiple = false ): void
11391147 {
11401148 try {
11411149 $ select = new WebDriverSelect ($ element );
@@ -1163,9 +1171,10 @@ private function selectOptionOnElement(WebDriverElement $element, string $value,
11631171 *
11641172 * Note: this implementation does not trigger a change event after deselecting the elements.
11651173 *
1174+ * @param TWebDriverElement $element
11661175 * @throws DriverException
11671176 */
1168- private function deselectAllOptions (WebDriverElement $ element ): void
1177+ private function deselectAllOptions ($ element ): void
11691178 {
11701179 try {
11711180 (new WebDriverSelect ($ element ))->deselectAll ();
@@ -1180,10 +1189,11 @@ private function deselectAllOptions(WebDriverElement $element): void
11801189 }
11811190
11821191 /**
1192+ * @param TWebDriverElement $element
11831193 * @throws DriverException
11841194 */
11851195 private function ensureInputType (
1186- WebDriverElement $ element ,
1196+ $ element ,
11871197 #[Language('XPath ' )]
11881198 string $ xpath ,
11891199 string $ type ,
@@ -1223,10 +1233,11 @@ private function jsonEncode($value, string $action, string $field): string
12231233 }
12241234
12251235 /**
1236+ * @param TWebDriverElement $element
12261237 * @param mixed $value
12271238 * @throws DriverException
12281239 */
1229- private function setElementDomProperty (WebDriverElement $ element , string $ property , $ value ): void
1240+ private function setElementDomProperty ($ element , string $ property , $ value ): void
12301241 {
12311242 $ this ->executeJsOnElement (
12321243 $ element ,
@@ -1235,13 +1246,14 @@ private function setElementDomProperty(WebDriverElement $element, string $proper
12351246 }
12361247
12371248 /**
1249+ * @param TWebDriverElement $element
12381250 * @return mixed
12391251 * @throws DriverException
12401252 */
1241- private function getElementDomProperty (RemoteWebElement $ element , string $ property )
1253+ private function getElementDomProperty ($ element , string $ property )
12421254 {
12431255 try {
1244- return $ this ->getWebDriver ()-> isW3cCompliant ()
1256+ return $ this ->isW3cCompliant ()
12451257 ? $ element ->getDomProperty ($ property )
12461258 : $ this ->executeJsOnElement ($ element , "return arguments[0][' $ property'] " );
12471259 } catch (UnsupportedOperationException $ e ) {
@@ -1270,5 +1282,14 @@ private function getAsString($value, string $name): string
12701282 return (string )$ value ;
12711283 }
12721284
1285+ private function isW3cCompliant (): bool
1286+ {
1287+ if (!method_exists ($ this ->getWebDriver (), 'isW3cCompliant ' )) {
1288+ throw new DriverException ('Base driver must implement an `isW3cCompliant` method that returns a boolean. ' );
1289+ }
1290+
1291+ return (bool )$ this ->getWebDriver ()->isW3cCompliant ();
1292+ }
1293+
12731294 // </editor-fold>
12741295}
0 commit comments