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,113 @@ 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+ var_dump ($ xpath );
297+
298+ $ nodeElements = $ this ->find ($ xpath );
299+ if (count ($ nodeElements ) === 0 ) {
300+ throw new DriverException (sprintf ('XPath "%s" built from WebDriver element did not find any element ' , $ xpath ));
301+ }
302+ if (count ($ nodeElements ) > 1 ) {
303+ throw new DriverException (sprintf ('XPath "%s" built from WebDriver element find more than one element ' , $ xpath ));
304+ }
305+ $ nodeElement = reset ($ nodeElements );
306+
307+ // DEBUG only
308+ if ($ this ->findElement ($ nodeElement ->getXpath ())->getID () !== $ element ->getId ()) {
309+ throw new DriverException (sprintf ('XPath "%s" built from WebDriver element cannot find the same element ' , $ xpath ));
310+ }
311+
312+ return $ nodeElement ;
313+ }
314+
315+ /**
316+ * Serialize execute arguments (containing web elements)
317+ *
318+ * @see https://w3c.github.io/webdriver/#executing-script
319+ *
320+ * @param array $args
321+ *
322+ * @return array
323+ */
324+ private function serializeExecuteArguments (array $ args )
325+ {
326+ foreach ($ args as $ k => $ v ) {
327+ if ($ v instanceof NodeElement) {
328+ $ args [$ k ] = $ this ->findElement ($ v ->getXpath ());
329+ } elseif (is_array ($ v )) {
330+ $ args [$ k ] = $ this ->serializeExecuteArguments ($ v );
331+ }
332+ }
333+
334+ return $ args ;
335+ }
336+
337+ /**
338+ * Unserialize execute result (containing web elements)
339+ *
340+ * @param mixed $data
341+ *
342+ * @return mixed
343+ */
344+ private function unserializeExecuteResult ($ data )
345+ {
346+ if ($ data instanceof Element) {
347+ return $ this ->createMinkElementFromWebDriverElement ($ data );
348+ } elseif (is_array ($ data )) {
349+ foreach ($ data as $ k => $ v ) {
350+ $ data [$ k ] = $ this ->unserializeExecuteResult ($ v );
351+ }
352+ }
353+
354+ return $ data ;
355+ }
356+
249357 /**
250358 * Executes JS on a given element - pass in a js script string and {{ELEMENT}} will
251359 * be replaced with a reference to the result of the $xpath query
@@ -284,11 +392,11 @@ private function executeJsOnElement(Element $element, $script, $sync = true)
284392 'args ' => array ($ element ),
285393 );
286394
287- if ( $ sync) {
288- return $ this ->wdSession ->execute ($ options );
289- }
395+ $ result = $ sync
396+ ? $ this ->wdSession ->execute ($ options )
397+ : $ this -> wdSession -> execute_async ( $ options );
290398
291- return $ this ->wdSession -> execute_async ( $ options );
399+ return $ this ->unserializeExecuteResult ( $ result );
292400 }
293401
294402 /**
@@ -585,7 +693,7 @@ public function getValue($xpath)
585693 }
586694
587695 if ('input ' === $ elementName && 'radio ' === $ elementType ) {
588- $ script = <<<JS
696+ $ script = <<<'JS'
589697var node = {{ELEMENT}},
590698 value = null;
591699
@@ -611,7 +719,7 @@ public function getValue($xpath)
611719 // Using $element->attribute('value') on a select only returns the first selected option
612720 // even when it is a multiple select, so a custom retrieval is needed.
613721 if ('select ' === $ elementName && $ element ->attribute ('multiple ' )) {
614- $ script = <<<JS
722+ $ script = <<<'JS'
615723var node = {{ELEMENT}},
616724 value = [];
617725
@@ -697,7 +805,7 @@ public function setValue($xpath, $value)
697805 // has lost focus in the meanwhile. If the element has lost focus
698806 // already then there is nothing to do as this will already have caused
699807 // the triggering of the change event for that element.
700- $ script = <<<JS
808+ $ script = <<<'JS'
701809var node = {{ELEMENT}};
702810if (document.activeElement === node) {
703811 document.activeElement.blur();
@@ -917,7 +1025,7 @@ public function dragTo($sourceXpath, $destinationXpath)
9171025 'element ' => $ source ->getID ()
9181026 ));
9191027
920- $ script = <<<JS
1028+ $ script = <<<'JS'
9211029(function (element) {
9221030 var event = document.createEvent("HTMLEvents");
9231031
@@ -935,7 +1043,7 @@ public function dragTo($sourceXpath, $destinationXpath)
9351043 ));
9361044 $ this ->wdSession ->buttonup ();
9371045
938- $ script = <<<JS
1046+ $ script = <<<'JS'
9391047(function (element) {
9401048 var event = document.createEvent("HTMLEvents");
9411049
@@ -951,39 +1059,50 @@ public function dragTo($sourceXpath, $destinationXpath)
9511059 /**
9521060 * {@inheritdoc}
9531061 */
954- public function executeScript ($ script )
1062+ public function executeScript ($ script, array $ args = [] )
9551063 {
9561064 if (preg_match ('/^function[\s\(]/ ' , $ script )) {
9571065 $ script = preg_replace ('/;$/ ' , '' , $ script );
9581066 $ script = '( ' . $ script . ') ' ;
9591067 }
9601068
961- $ this ->wdSession ->execute (array ('script ' => $ script , 'args ' => array ()));
1069+ $ this ->wdSession ->execute (array (
1070+ 'script ' => $ script ,
1071+ 'args ' => $ this ->serializeExecuteArguments ($ args ),
1072+ ));
9621073 }
9631074
9641075 /**
9651076 * {@inheritdoc}
9661077 */
967- public function evaluateScript ($ script )
1078+ public function evaluateScript ($ script, array $ args = [] )
9681079 {
9691080 if (0 !== strpos (trim ($ script ), 'return ' )) {
9701081 $ script = 'return ' . $ script ;
9711082 }
9721083
973- return $ this ->wdSession ->execute (array ('script ' => $ script , 'args ' => array ()));
1084+ $ result = $ this ->wdSession ->execute (array (
1085+ 'script ' => $ script ,
1086+ 'args ' => $ this ->serializeExecuteArguments ($ args ),
1087+ ));
1088+
1089+ return $ this ->unserializeExecuteResult ($ result );
9741090 }
9751091
9761092 /**
9771093 * {@inheritdoc}
9781094 */
979- public function wait ($ timeout , $ condition )
1095+ public function wait ($ timeout , $ condition, array $ args = [] )
9801096 {
9811097 $ script = 'return ( ' . rtrim ($ condition , " \t\n\r; " ) . '); ' ;
9821098 $ start = microtime (true );
9831099 $ end = $ start + $ timeout / 1000.0 ;
9841100
9851101 do {
986- $ result = $ this ->wdSession ->execute (array ('script ' => $ script , 'args ' => array ()));
1102+ $ result = $ this ->wdSession ->execute (array (
1103+ 'script ' => $ script ,
1104+ 'args ' => $ this ->serializeExecuteArguments ($ args ),
1105+ ));
9871106 if ($ result ) {
9881107 break ;
9891108 }
@@ -1130,7 +1249,7 @@ private function selectOptionOnElement(Element $element, $value, $multiple = fal
11301249 */
11311250 private function deselectAllOptions (Element $ element )
11321251 {
1133- $ script = <<<JS
1252+ $ script = <<<'JS'
11341253var node = {{ELEMENT}};
11351254var i, l = node.options.length;
11361255for (i = 0; i < l; i++) {
0 commit comments