Skip to content

Commit 836909c

Browse files
committed
Convert element to NodeElement from execute result
1 parent 4ee4eca commit 836909c

File tree

1 file changed

+135
-16
lines changed

1 file changed

+135
-16
lines changed

src/Selenium2Driver.php

Lines changed: 135 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
namespace Behat\Mink\Driver;
1212

13+
use Behat\Mink\Element\NodeElement;
1314
use Behat\Mink\Exception\DriverException;
1415
use Behat\Mink\Selector\Xpath\Escaper;
1516
use 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'
589697
var 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'
615723
var 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'
701809
var node = {{ELEMENT}};
702810
if (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'
11341253
var node = {{ELEMENT}};
11351254
var i, l = node.options.length;
11361255
for (i = 0; i < l; i++) {

0 commit comments

Comments
 (0)