Skip to content

Commit

Permalink
Convert element to NodeElement from execute result
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Apr 20, 2022
1 parent 4ee4eca commit 836909c
Showing 1 changed file with 135 additions and 16 deletions.
151 changes: 135 additions & 16 deletions src/Selenium2Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

namespace Behat\Mink\Driver;

use Behat\Mink\Element\NodeElement;
use Behat\Mink\Exception\DriverException;
use Behat\Mink\Selector\Xpath\Escaper;
use WebDriver\Element;
Expand Down Expand Up @@ -246,6 +247,113 @@ protected static function charToOptions($char, $modifier = null)
return json_encode($options);
}

/**
* Create Mink element from WebDriver element.
*
* @return NodeElement[]
*
* @throws DriverException When the operation cannot be done
*/
protected function createMinkElementFromWebDriverElement(Element $element)
{
// WebDriver element contains only a temporary ID assigned by Selenium,
// to create a Mink element we must build a xpath for it first
$script = <<<'JS'
var buildXpathFromElement;
buildXpathFromElement = function (element) {
var tagNameLc = element.tagName.toLowerCase();
if (element.parentElement === null) {
return '/' + tagNameLc;
}
if (element.id && document.querySelectorAll(tagNameLc + '#' + element.id).length === 1) {
return '//' + tagNameLc + '[@id=\'' + element.id + '\']';
}
var children = element.parentElement.children;
var pos = 0;
for (var i = 0; i < children.length; i++) {
if (children[i].tagName.toLowerCase() === tagNameLc) {
pos++;
if (children[i] === element) {
break;
}
}
}
var xpath = buildXpathFromElement(element.parentElement) + '/' + tagNameLc + '[' + pos + ']';
return xpath;
};
return buildXpathFromElement(arguments[0]);
JS;
$xpath = $this->wdSession->execute(array(
'script' => $script,
'args' => array($element),
));

var_dump($xpath);

$nodeElements = $this->find($xpath);
if (count($nodeElements) === 0) {
throw new DriverException(sprintf('XPath "%s" built from WebDriver element did not find any element', $xpath));
}
if (count($nodeElements) > 1) {
throw new DriverException(sprintf('XPath "%s" built from WebDriver element find more than one element', $xpath));
}
$nodeElement = reset($nodeElements);

// DEBUG only
if ($this->findElement($nodeElement->getXpath())->getID() !== $element->getId()) {
throw new DriverException(sprintf('XPath "%s" built from WebDriver element cannot find the same element', $xpath));
}

return $nodeElement;
}

/**
* Serialize execute arguments (containing web elements)
*
* @see https://w3c.github.io/webdriver/#executing-script
*
* @param array $args
*
* @return array
*/
private function serializeExecuteArguments(array $args)
{
foreach ($args as $k => $v) {
if ($v instanceof NodeElement) {
$args[$k] = $this->findElement($v->getXpath());
} elseif (is_array($v)) {
$args[$k] = $this->serializeExecuteArguments($v);
}
}

return $args;
}

/**
* Unserialize execute result (containing web elements)
*
* @param mixed $data
*
* @return mixed
*/
private function unserializeExecuteResult($data)
{
if ($data instanceof Element) {
return $this->createMinkElementFromWebDriverElement($data);
} elseif (is_array($data)) {
foreach ($data as $k => $v) {
$data[$k] = $this->unserializeExecuteResult($v);
}
}

return $data;
}

/**
* Executes JS on a given element - pass in a js script string and {{ELEMENT}} will
* be replaced with a reference to the result of the $xpath query
Expand Down Expand Up @@ -284,11 +392,11 @@ private function executeJsOnElement(Element $element, $script, $sync = true)
'args' => array($element),
);

if ($sync) {
return $this->wdSession->execute($options);
}
$result = $sync
? $this->wdSession->execute($options)
: $this->wdSession->execute_async($options);

return $this->wdSession->execute_async($options);
return $this->unserializeExecuteResult($result);
}

/**
Expand Down Expand Up @@ -585,7 +693,7 @@ public function getValue($xpath)
}

if ('input' === $elementName && 'radio' === $elementType) {
$script = <<<JS
$script = <<<'JS'
var node = {{ELEMENT}},
value = null;
Expand All @@ -611,7 +719,7 @@ public function getValue($xpath)
// Using $element->attribute('value') on a select only returns the first selected option
// even when it is a multiple select, so a custom retrieval is needed.
if ('select' === $elementName && $element->attribute('multiple')) {
$script = <<<JS
$script = <<<'JS'
var node = {{ELEMENT}},
value = [];
Expand Down Expand Up @@ -697,7 +805,7 @@ public function setValue($xpath, $value)
// has lost focus in the meanwhile. If the element has lost focus
// already then there is nothing to do as this will already have caused
// the triggering of the change event for that element.
$script = <<<JS
$script = <<<'JS'
var node = {{ELEMENT}};
if (document.activeElement === node) {
document.activeElement.blur();
Expand Down Expand Up @@ -917,7 +1025,7 @@ public function dragTo($sourceXpath, $destinationXpath)
'element' => $source->getID()
));

$script = <<<JS
$script = <<<'JS'
(function (element) {
var event = document.createEvent("HTMLEvents");
Expand All @@ -935,7 +1043,7 @@ public function dragTo($sourceXpath, $destinationXpath)
));
$this->wdSession->buttonup();

$script = <<<JS
$script = <<<'JS'
(function (element) {
var event = document.createEvent("HTMLEvents");
Expand All @@ -951,39 +1059,50 @@ public function dragTo($sourceXpath, $destinationXpath)
/**
* {@inheritdoc}
*/
public function executeScript($script)
public function executeScript($script, array $args = [])
{
if (preg_match('/^function[\s\(]/', $script)) {
$script = preg_replace('/;$/', '', $script);
$script = '(' . $script . ')';
}

$this->wdSession->execute(array('script' => $script, 'args' => array()));
$this->wdSession->execute(array(
'script' => $script,
'args' => $this->serializeExecuteArguments($args),
));
}

/**
* {@inheritdoc}
*/
public function evaluateScript($script)
public function evaluateScript($script, array $args = [])
{
if (0 !== strpos(trim($script), 'return ')) {
$script = 'return ' . $script;
}

return $this->wdSession->execute(array('script' => $script, 'args' => array()));
$result = $this->wdSession->execute(array(
'script' => $script,
'args' => $this->serializeExecuteArguments($args),
));

return $this->unserializeExecuteResult($result);
}

/**
* {@inheritdoc}
*/
public function wait($timeout, $condition)
public function wait($timeout, $condition, array $args = [])
{
$script = 'return (' . rtrim($condition, " \t\n\r;") . ');';
$start = microtime(true);
$end = $start + $timeout / 1000.0;

do {
$result = $this->wdSession->execute(array('script' => $script, 'args' => array()));
$result = $this->wdSession->execute(array(
'script' => $script,
'args' => $this->serializeExecuteArguments($args),
));
if ($result) {
break;
}
Expand Down Expand Up @@ -1130,7 +1249,7 @@ private function selectOptionOnElement(Element $element, $value, $multiple = fal
*/
private function deselectAllOptions(Element $element)
{
$script = <<<JS
$script = <<<'JS'
var node = {{ELEMENT}};
var i, l = node.options.length;
for (i = 0; i < l; i++) {
Expand Down

0 comments on commit 836909c

Please sign in to comment.