1010
1111namespace Behat \Mink \Driver ;
1212
13+ use Behat \Mink \Element \NodeElement ;
1314use Behat \Mink \Exception \DriverException ;
1415use Behat \Mink \Selector \Xpath \Escaper ;
1516use WebDriver \Element ;
@@ -246,6 +247,111 @@ protected static function charToOptions($char, $modifier = null)
246247 return json_encode ($ options );
247248 }
248249
250+ /**
251+ * Create Mink element from WebDriver element.
252+ *
253+ * @return NodeElement[]
254+ *
255+ * @throws DriverException When the operation cannot be done
256+ */
257+ protected function createMinkElementFromWebDriverElement (Element $ element )
258+ {
259+ // WebDriver element contains only a temporary ID assigned by Selenium,
260+ // to create a Mink element we must build a xpath for it first
261+ $ script = <<<'JS'
262+ var buildXpathFromElement;
263+ buildXpathFromElement = function (element) {
264+ var tagNameLc = element.tagName.toLowerCase();
265+ if (element.parentElement === null) {
266+ return '/' + tagNameLc;
267+ }
268+
269+ if (element.id && document.querySelectorAll(tagNameLc + '#' + element.id).length === 1) {
270+ return '//' + tagNameLc + '[@id=\'' + element.id + '\']';
271+ }
272+
273+ var children = element.parentElement.children;
274+ var pos = 0;
275+ for (var i = 0; i < children.length; i++) {
276+ if (children[i].tagName.toLowerCase() === tagNameLc) {
277+ pos++;
278+ if (children[i] === element) {
279+ break;
280+ }
281+ }
282+ }
283+
284+ var xpath = buildXpathFromElement(element.parentElement) + '/' + tagNameLc + '[' + pos + ']';
285+
286+ return xpath;
287+ };
288+
289+ return buildXpathFromElement(arguments[0]);
290+ JS;
291+ $ xpath = $ this ->wdSession ->execute (array (
292+ 'script ' => $ script ,
293+ 'args ' => array ($ element ),
294+ ));
295+
296+ $ minkElements = $ this ->find ($ xpath );
297+ if (count ($ minkElements ) === 0 ) {
298+ throw new DriverException (sprintf ('XPath "%s" built from WebDriver element did not find any element ' , $ xpath ));
299+ }
300+ if (count ($ minkElements ) > 1 ) {
301+ throw new DriverException (sprintf ('XPath "%s" built from WebDriver element find more than one element ' , $ xpath ));
302+ }
303+ $ minkElement = reset ($ minkElements );
304+
305+ // DEBUG only
306+ if ($ this ->findElement ($ minkElement ->getXpath ())->getID () !== $ element ->getId ()) {
307+ throw new DriverException (sprintf ('XPath "%s" built from WebDriver element cannot find the same element ' , $ xpath ));
308+ }
309+
310+ return $ minkElement ;
311+ }
312+
313+ /**
314+ * Serialize execute arguments (containing web elements)
315+ *
316+ * @see https://w3c.github.io/webdriver/#executing-script
317+ *
318+ * @param array $args
319+ *
320+ * @return array
321+ */
322+ private function serializeExecuteArguments (array $ args )
323+ {
324+ foreach ($ args as $ k => $ v ) {
325+ if ($ v instanceof NodeElement) {
326+ $ args [$ k ] = $ this ->findElement ($ v ->getXpath ());
327+ } elseif (is_array ($ v )) {
328+ $ args [$ k ] = $ this ->serializeExecuteArguments ($ v );
329+ }
330+ }
331+
332+ return $ args ;
333+ }
334+
335+ /**
336+ * Unserialize execute result (containing web elements)
337+ *
338+ * @param mixed $data
339+ *
340+ * @return mixed
341+ */
342+ private function unserializeExecuteResult ($ data )
343+ {
344+ if ($ data instanceof Element) {
345+ return $ this ->createMinkElementFromWebDriverElement ($ data );
346+ } elseif (is_array ($ data )) {
347+ foreach ($ data as $ k => $ v ) {
348+ $ data [$ k ] = $ this ->unserializeExecuteResult ($ v );
349+ }
350+ }
351+
352+ return $ data ;
353+ }
354+
249355 /**
250356 * Executes JS on a given element - pass in a js script string and {{ELEMENT}} will
251357 * be replaced with a reference to the result of the $xpath query
@@ -284,11 +390,11 @@ private function executeJsOnElement(Element $element, $script, $sync = true)
284390 'args ' => array ($ element ),
285391 );
286392
287- if ( $ sync) {
288- return $ this ->wdSession ->execute ($ options );
289- }
393+ $ result = $ sync
394+ ? $ this ->wdSession ->execute ($ options )
395+ : $ this -> wdSession -> execute_async ( $ options );
290396
291- return $ this ->wdSession -> execute_async ( $ options );
397+ return $ this ->unserializeExecuteResult ( $ result );
292398 }
293399
294400 /**
@@ -585,7 +691,7 @@ public function getValue($xpath)
585691 }
586692
587693 if ('input ' === $ elementName && 'radio ' === $ elementType ) {
588- $ script = <<<JS
694+ $ script = <<<'JS'
589695var node = {{ELEMENT}},
590696 value = null;
591697
@@ -611,7 +717,7 @@ public function getValue($xpath)
611717 // Using $element->attribute('value') on a select only returns the first selected option
612718 // even when it is a multiple select, so a custom retrieval is needed.
613719 if ('select ' === $ elementName && $ element ->attribute ('multiple ' )) {
614- $ script = <<<JS
720+ $ script = <<<'JS'
615721var node = {{ELEMENT}},
616722 value = [];
617723
@@ -697,7 +803,7 @@ public function setValue($xpath, $value)
697803 // has lost focus in the meanwhile. If the element has lost focus
698804 // already then there is nothing to do as this will already have caused
699805 // the triggering of the change event for that element.
700- $ script = <<<JS
806+ $ script = <<<'JS'
701807var node = {{ELEMENT}};
702808if (document.activeElement === node) {
703809 document.activeElement.blur();
@@ -917,7 +1023,7 @@ public function dragTo($sourceXpath, $destinationXpath)
9171023 'element ' => $ source ->getID ()
9181024 ));
9191025
920- $ script = <<<JS
1026+ $ script = <<<'JS'
9211027(function (element) {
9221028 var event = document.createEvent("HTMLEvents");
9231029
@@ -935,7 +1041,7 @@ public function dragTo($sourceXpath, $destinationXpath)
9351041 ));
9361042 $ this ->wdSession ->buttonup ();
9371043
938- $ script = <<<JS
1044+ $ script = <<<'JS'
9391045(function (element) {
9401046 var event = document.createEvent("HTMLEvents");
9411047
@@ -951,39 +1057,50 @@ public function dragTo($sourceXpath, $destinationXpath)
9511057 /**
9521058 * {@inheritdoc}
9531059 */
954- public function executeScript ($ script )
1060+ public function executeScript ($ script, array $ args = [] )
9551061 {
9561062 if (preg_match ('/^function[\s\(]/ ' , $ script )) {
9571063 $ script = preg_replace ('/;$/ ' , '' , $ script );
9581064 $ script = '( ' . $ script . ') ' ;
9591065 }
9601066
961- $ this ->wdSession ->execute (array ('script ' => $ script , 'args ' => array ()));
1067+ $ this ->wdSession ->execute (array (
1068+ 'script ' => $ script ,
1069+ 'args ' => $ this ->serializeExecuteArguments ($ args ),
1070+ ));
9621071 }
9631072
9641073 /**
9651074 * {@inheritdoc}
9661075 */
967- public function evaluateScript ($ script )
1076+ public function evaluateScript ($ script, array $ args = [] )
9681077 {
9691078 if (0 !== strpos (trim ($ script ), 'return ' )) {
9701079 $ script = 'return ' . $ script ;
9711080 }
9721081
973- return $ this ->wdSession ->execute (array ('script ' => $ script , 'args ' => array ()));
1082+ $ result = $ this ->wdSession ->execute (array (
1083+ 'script ' => $ script ,
1084+ 'args ' => $ this ->serializeExecuteArguments ($ args ),
1085+ ));
1086+
1087+ return $ this ->unserializeExecuteResult ($ result );
9741088 }
9751089
9761090 /**
9771091 * {@inheritdoc}
9781092 */
979- public function wait ($ timeout , $ condition )
1093+ public function wait ($ timeout , $ condition, array $ args = [] )
9801094 {
9811095 $ script = 'return ( ' . rtrim ($ condition , " \t\n\r; " ) . '); ' ;
9821096 $ start = microtime (true );
9831097 $ end = $ start + $ timeout / 1000.0 ;
9841098
9851099 do {
986- $ result = $ this ->wdSession ->execute (array ('script ' => $ script , 'args ' => array ()));
1100+ $ result = $ this ->wdSession ->execute (array (
1101+ 'script ' => $ script ,
1102+ 'args ' => $ this ->serializeExecuteArguments ($ args ),
1103+ ));
9871104 if ($ result ) {
9881105 break ;
9891106 }
@@ -1130,7 +1247,7 @@ private function selectOptionOnElement(Element $element, $value, $multiple = fal
11301247 */
11311248 private function deselectAllOptions (Element $ element )
11321249 {
1133- $ script = <<<JS
1250+ $ script = <<<'JS'
11341251var node = {{ELEMENT}};
11351252var i, l = node.options.length;
11361253for (i = 0; i < l; i++) {
0 commit comments