@@ -248,11 +248,100 @@ public function click(ElementInterface $element)
248
248
249
249
$ wrapperElement = $ this ->getNativeElement ($ element );
250
250
$ this ->driver ->moveto ($ wrapperElement );
251
- $ wrapperElement -> click ( );
251
+ $ this -> tryClick ( $ wrapperElement );
252
252
253
253
$ this ->eventManager ->dispatchEvent (['click_after ' ], [__METHOD__ , $ absoluteSelector ]);
254
254
}
255
255
256
+ /**
257
+ * Try to click on element.
258
+ *
259
+ * @param \PHPUnit_Extensions_Selenium2TestCase_Element $element
260
+ * @param int $parentDepth [optional]
261
+ * @param string $blockedElementSelector [optional]
262
+ * @throws \PHPUnit_Extensions_Selenium2TestCase_WebDriverException
263
+ */
264
+ private function tryClick (
265
+ \PHPUnit_Extensions_Selenium2TestCase_Element $ element ,
266
+ $ parentDepth = 0 ,
267
+ $ blockedElementSelector = ''
268
+ ) {
269
+ try {
270
+ $ element ->click ();
271
+ } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $ e ) {
272
+ $ elementSelector = $ this ->prepareBlockedElementSelector ($ e ->getMessage ());
273
+ $ js = $ this ->prepareJSForScrollToUpByElement ($ elementSelector , $ parentDepth , $ blockedElementSelector );
274
+ // Scroll action
275
+ if (!$ this ->driver ->execute (['script ' => $ js , 'args ' => []])) {
276
+ throw $ e ;
277
+ }
278
+
279
+ $ this ->tryClick ($ element , ++$ parentDepth , $ elementSelector );
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Prepare blocked element selector base on exception message.
285
+ * Example return: "element[attribute1="value"][attribute2="value"]..."
286
+ *
287
+ * @param string $exceptionMessage
288
+ * @return string
289
+ */
290
+ private function prepareBlockedElementSelector ($ exceptionMessage )
291
+ {
292
+ // Get html code
293
+ $ htmlCode = substr ($ exceptionMessage , 0 , strpos ($ exceptionMessage , 'Command duration ' ));
294
+ // Find element name
295
+ preg_match ('/<(\w+)/ ' , $ htmlCode , $ matches );
296
+ $ elementSelector = isset ($ matches [1 ]) ? $ matches [1 ] : '* ' ;
297
+ // Find element selector
298
+ if (preg_match_all ('/(([^ ]+)="([^"]+)")/ ' , $ htmlCode , $ matches )) {
299
+ foreach ($ matches [0 ] as $ match ) {
300
+ $ match = str_replace ('\n ' , '' , $ match ); // skipped '\n'
301
+ $ match = str_replace ("' " , '" ' , $ match ); // escaped quotes
302
+ $ elementSelector .= "[ {$ match }] " ;
303
+ }
304
+ }
305
+
306
+ return $ elementSelector ;
307
+ }
308
+
309
+ /**
310
+ * Prepare JS script code for scroll to up relative element.
311
+ *
312
+ * @param string $elementSelector
313
+ * @param int $parentDepth
314
+ * @param string $blockedElementSelector
315
+ * @return string
316
+ */
317
+ private function prepareJSForScrollToUpByElement ($ elementSelector , $ parentDepth , $ blockedElementSelector )
318
+ {
319
+ return "var element = document.querySelector(' $ elementSelector'),
320
+ height = 100;
321
+
322
+ // If element is founded
323
+ if (element !== null) {
324
+ /* If attempt isn't first and previous element selector is equal current
325
+ need to scroll by parent element */
326
+ if ( $ parentDepth && ' $ blockedElementSelector' == ' $ elementSelector') {
327
+ for (var i = 0; i <= $ parentDepth; i++) {
328
+ element = element.parentElement;
329
+ }
330
+ }
331
+ // If element is 'body', then need scroll and throw exception
332
+ if (element === document.querySelector('body')) {
333
+ return false;
334
+ }
335
+
336
+ var elementHeight = element.offsetHeight;
337
+ height = (elementHeight !== null) ? elementHeight : height
338
+ }
339
+
340
+ scrollBy(0, -height);
341
+
342
+ return true; " ;
343
+ }
344
+
256
345
/**
257
346
* Double click.
258
347
*
@@ -668,15 +757,17 @@ public function switchToFrame(Locator $locator = null)
668
757
}
669
758
670
759
/**
671
- * Close the current window.
760
+ * Close the current window or specified one .
672
761
*
762
+ * @param string|null $handle [optional]
673
763
* @return void
674
764
*/
675
- public function closeWindow ()
765
+ public function closeWindow ($ handle = null )
676
766
{
677
767
$ windowHandles = $ this ->driver ->windowHandles ();
678
768
if (count ($ windowHandles ) > 1 ) {
679
- $ this ->driver ->window (end ($ windowHandles ));
769
+ $ windowHandle = $ handle !== null ? $ handle : end ($ windowHandles );
770
+ $ this ->driver ->window ($ windowHandle );
680
771
$ this ->driver ->closeWindow ();
681
772
$ this ->driver ->window (reset ($ windowHandles ));
682
773
} else {
@@ -685,14 +776,36 @@ public function closeWindow()
685
776
}
686
777
687
778
/**
688
- * Select window by its name .
779
+ * Changes the focus to the specified window or to the latest one .
689
780
*
781
+ * @param string|null $handle [optional]
690
782
* @return void
691
783
*/
692
- public function selectWindow ()
784
+ public function selectWindow ($ handle = null )
693
785
{
694
786
$ windowHandles = $ this ->driver ->windowHandles ();
695
- $ this ->driver ->window (end ($ windowHandles ));
787
+ $ windowHandle = $ handle !== null ? $ handle : end ($ windowHandles );
788
+ $ this ->driver ->window ($ windowHandle );
789
+ }
790
+
791
+ /**
792
+ * Retrieves the current window handle.
793
+ *
794
+ * @return string
795
+ */
796
+ public function getCurrentWindow ()
797
+ {
798
+ return $ this ->driver ->windowHandle ();
799
+ }
800
+
801
+ /**
802
+ * Retrieves a list of all available window handles.
803
+ *
804
+ * @return array
805
+ */
806
+ public function getWindowHandles ()
807
+ {
808
+ return $ this ->driver ->windowHandles ();
696
809
}
697
810
698
811
/**
0 commit comments