annotate vendor/behat/mink-selenium2-driver/src/Selenium2Driver.php @ 16:c2387f117808

Routine composer update
author Chris Cannam
date Tue, 10 Jul 2018 15:07:59 +0100
parents 1fec387a4317
children 129ea1e6d783
rev   line source
Chris@14 1 <?php
Chris@14 2
Chris@14 3 /*
Chris@14 4 * This file is part of the Behat\Mink.
Chris@14 5 * (c) Konstantin Kudryashov <ever.zet@gmail.com>
Chris@14 6 *
Chris@14 7 * For the full copyright and license information, please view the LICENSE
Chris@14 8 * file that was distributed with this source code.
Chris@14 9 */
Chris@14 10
Chris@14 11 namespace Behat\Mink\Driver;
Chris@14 12
Chris@14 13 use Behat\Mink\Exception\DriverException;
Chris@14 14 use Behat\Mink\Selector\Xpath\Escaper;
Chris@14 15 use WebDriver\Element;
Chris@14 16 use WebDriver\Exception\NoSuchElement;
Chris@14 17 use WebDriver\Exception\UnknownCommand;
Chris@14 18 use WebDriver\Exception\UnknownError;
Chris@14 19 use WebDriver\Exception;
Chris@14 20 use WebDriver\Key;
Chris@14 21 use WebDriver\WebDriver;
Chris@14 22
Chris@14 23 /**
Chris@14 24 * Selenium2 driver.
Chris@14 25 *
Chris@14 26 * @author Pete Otaqui <pete@otaqui.com>
Chris@14 27 */
Chris@14 28 class Selenium2Driver extends CoreDriver
Chris@14 29 {
Chris@14 30 /**
Chris@14 31 * Whether the browser has been started
Chris@14 32 * @var Boolean
Chris@14 33 */
Chris@14 34 private $started = false;
Chris@14 35
Chris@14 36 /**
Chris@14 37 * The WebDriver instance
Chris@14 38 * @var WebDriver
Chris@14 39 */
Chris@14 40 private $webDriver;
Chris@14 41
Chris@14 42 /**
Chris@14 43 * @var string
Chris@14 44 */
Chris@14 45 private $browserName;
Chris@14 46
Chris@14 47 /**
Chris@14 48 * @var array
Chris@14 49 */
Chris@14 50 private $desiredCapabilities;
Chris@14 51
Chris@14 52 /**
Chris@14 53 * The WebDriverSession instance
Chris@14 54 * @var \WebDriver\Session
Chris@14 55 */
Chris@14 56 private $wdSession;
Chris@14 57
Chris@14 58 /**
Chris@14 59 * The timeout configuration
Chris@14 60 * @var array
Chris@14 61 */
Chris@14 62 private $timeouts = array();
Chris@14 63
Chris@14 64 /**
Chris@14 65 * @var Escaper
Chris@14 66 */
Chris@14 67 private $xpathEscaper;
Chris@14 68
Chris@14 69 /**
Chris@14 70 * Instantiates the driver.
Chris@14 71 *
Chris@14 72 * @param string $browserName Browser name
Chris@14 73 * @param array $desiredCapabilities The desired capabilities
Chris@14 74 * @param string $wdHost The WebDriver host
Chris@14 75 */
Chris@14 76 public function __construct($browserName = 'firefox', $desiredCapabilities = null, $wdHost = 'http://localhost:4444/wd/hub')
Chris@14 77 {
Chris@14 78 $this->setBrowserName($browserName);
Chris@14 79 $this->setDesiredCapabilities($desiredCapabilities);
Chris@14 80 $this->setWebDriver(new WebDriver($wdHost));
Chris@14 81 $this->xpathEscaper = new Escaper();
Chris@14 82 }
Chris@14 83
Chris@14 84 /**
Chris@14 85 * Sets the browser name
Chris@14 86 *
Chris@14 87 * @param string $browserName the name of the browser to start, default is 'firefox'
Chris@14 88 */
Chris@14 89 protected function setBrowserName($browserName = 'firefox')
Chris@14 90 {
Chris@14 91 $this->browserName = $browserName;
Chris@14 92 }
Chris@14 93
Chris@14 94 /**
Chris@14 95 * Sets the desired capabilities - called on construction. If null is provided, will set the
Chris@14 96 * defaults as desired.
Chris@14 97 *
Chris@14 98 * See http://code.google.com/p/selenium/wiki/DesiredCapabilities
Chris@14 99 *
Chris@14 100 * @param array $desiredCapabilities an array of capabilities to pass on to the WebDriver server
Chris@14 101 *
Chris@14 102 * @throws DriverException
Chris@14 103 */
Chris@14 104 public function setDesiredCapabilities($desiredCapabilities = null)
Chris@14 105 {
Chris@14 106 if ($this->started) {
Chris@14 107 throw new DriverException("Unable to set desiredCapabilities, the session has already started");
Chris@14 108 }
Chris@14 109
Chris@14 110 if (null === $desiredCapabilities) {
Chris@14 111 $desiredCapabilities = array();
Chris@14 112 }
Chris@14 113
Chris@14 114 // Join $desiredCapabilities with defaultCapabilities
Chris@14 115 $desiredCapabilities = array_replace(self::getDefaultCapabilities(), $desiredCapabilities);
Chris@14 116
Chris@14 117 if (isset($desiredCapabilities['firefox'])) {
Chris@14 118 foreach ($desiredCapabilities['firefox'] as $capability => $value) {
Chris@14 119 switch ($capability) {
Chris@14 120 case 'profile':
Chris@14 121 $desiredCapabilities['firefox_'.$capability] = base64_encode(file_get_contents($value));
Chris@14 122 break;
Chris@14 123 default:
Chris@14 124 $desiredCapabilities['firefox_'.$capability] = $value;
Chris@14 125 }
Chris@14 126 }
Chris@14 127
Chris@14 128 unset($desiredCapabilities['firefox']);
Chris@14 129 }
Chris@14 130
Chris@14 131 // See https://sites.google.com/a/chromium.org/chromedriver/capabilities
Chris@14 132 if (isset($desiredCapabilities['chrome'])) {
Chris@14 133
Chris@14 134 $chromeOptions = array();
Chris@14 135
Chris@14 136 foreach ($desiredCapabilities['chrome'] as $capability => $value) {
Chris@14 137 if ($capability == 'switches') {
Chris@14 138 $chromeOptions['args'] = $value;
Chris@14 139 } else {
Chris@14 140 $chromeOptions[$capability] = $value;
Chris@14 141 }
Chris@14 142 $desiredCapabilities['chrome.'.$capability] = $value;
Chris@14 143 }
Chris@14 144
Chris@14 145 $desiredCapabilities['chromeOptions'] = $chromeOptions;
Chris@14 146
Chris@14 147 unset($desiredCapabilities['chrome']);
Chris@14 148 }
Chris@14 149
Chris@14 150 $this->desiredCapabilities = $desiredCapabilities;
Chris@14 151 }
Chris@14 152
Chris@14 153 /**
Chris@14 154 * Gets the desiredCapabilities
Chris@14 155 *
Chris@14 156 * @return array $desiredCapabilities
Chris@14 157 */
Chris@14 158 public function getDesiredCapabilities()
Chris@14 159 {
Chris@14 160 return $this->desiredCapabilities;
Chris@14 161 }
Chris@14 162
Chris@14 163 /**
Chris@14 164 * Sets the WebDriver instance
Chris@14 165 *
Chris@14 166 * @param WebDriver $webDriver An instance of the WebDriver class
Chris@14 167 */
Chris@14 168 public function setWebDriver(WebDriver $webDriver)
Chris@14 169 {
Chris@14 170 $this->webDriver = $webDriver;
Chris@14 171 }
Chris@14 172
Chris@14 173 /**
Chris@14 174 * Gets the WebDriverSession instance
Chris@14 175 *
Chris@14 176 * @return \WebDriver\Session
Chris@14 177 */
Chris@14 178 public function getWebDriverSession()
Chris@14 179 {
Chris@14 180 return $this->wdSession;
Chris@14 181 }
Chris@14 182
Chris@14 183 /**
Chris@14 184 * Returns the default capabilities
Chris@14 185 *
Chris@14 186 * @return array
Chris@14 187 */
Chris@14 188 public static function getDefaultCapabilities()
Chris@14 189 {
Chris@14 190 return array(
Chris@14 191 'browserName' => 'firefox',
Chris@14 192 'version' => '9',
Chris@14 193 'platform' => 'ANY',
Chris@14 194 'browserVersion' => '9',
Chris@14 195 'browser' => 'firefox',
Chris@14 196 'name' => 'Behat Test',
Chris@14 197 'deviceOrientation' => 'portrait',
Chris@14 198 'deviceType' => 'tablet',
Chris@14 199 'selenium-version' => '2.31.0'
Chris@14 200 );
Chris@14 201 }
Chris@14 202
Chris@14 203 /**
Chris@14 204 * Makes sure that the Syn event library has been injected into the current page,
Chris@14 205 * and return $this for a fluid interface,
Chris@14 206 *
Chris@14 207 * $this->withSyn()->executeJsOnXpath($xpath, $script);
Chris@14 208 *
Chris@14 209 * @return Selenium2Driver
Chris@14 210 */
Chris@14 211 protected function withSyn()
Chris@14 212 {
Chris@14 213 $hasSyn = $this->wdSession->execute(array(
Chris@14 214 'script' => 'return typeof window["Syn"]!=="undefined" && typeof window["Syn"].trigger!=="undefined"',
Chris@14 215 'args' => array()
Chris@14 216 ));
Chris@14 217
Chris@14 218 if (!$hasSyn) {
Chris@14 219 $synJs = file_get_contents(__DIR__.'/Resources/syn.js');
Chris@14 220 $this->wdSession->execute(array(
Chris@14 221 'script' => $synJs,
Chris@14 222 'args' => array()
Chris@14 223 ));
Chris@14 224 }
Chris@14 225
Chris@14 226 return $this;
Chris@14 227 }
Chris@14 228
Chris@14 229 /**
Chris@14 230 * Creates some options for key events
Chris@14 231 *
Chris@14 232 * @param string $char the character or code
Chris@14 233 * @param string $modifier one of 'shift', 'alt', 'ctrl' or 'meta'
Chris@14 234 *
Chris@14 235 * @return string a json encoded options array for Syn
Chris@14 236 */
Chris@14 237 protected static function charToOptions($char, $modifier = null)
Chris@14 238 {
Chris@14 239 $ord = ord($char);
Chris@14 240 if (is_numeric($char)) {
Chris@14 241 $ord = $char;
Chris@14 242 }
Chris@14 243
Chris@14 244 $options = array(
Chris@14 245 'keyCode' => $ord,
Chris@14 246 'charCode' => $ord
Chris@14 247 );
Chris@14 248
Chris@14 249 if ($modifier) {
Chris@14 250 $options[$modifier.'Key'] = 1;
Chris@14 251 }
Chris@14 252
Chris@14 253 return json_encode($options);
Chris@14 254 }
Chris@14 255
Chris@14 256 /**
Chris@14 257 * Executes JS on a given element - pass in a js script string and {{ELEMENT}} will
Chris@14 258 * be replaced with a reference to the result of the $xpath query
Chris@14 259 *
Chris@14 260 * @example $this->executeJsOnXpath($xpath, 'return {{ELEMENT}}.childNodes.length');
Chris@14 261 *
Chris@14 262 * @param string $xpath the xpath to search with
Chris@14 263 * @param string $script the script to execute
Chris@14 264 * @param Boolean $sync whether to run the script synchronously (default is TRUE)
Chris@14 265 *
Chris@14 266 * @return mixed
Chris@14 267 */
Chris@14 268 protected function executeJsOnXpath($xpath, $script, $sync = true)
Chris@14 269 {
Chris@14 270 return $this->executeJsOnElement($this->findElement($xpath), $script, $sync);
Chris@14 271 }
Chris@14 272
Chris@14 273 /**
Chris@14 274 * Executes JS on a given element - pass in a js script string and {{ELEMENT}} will
Chris@14 275 * be replaced with a reference to the element
Chris@14 276 *
Chris@14 277 * @example $this->executeJsOnXpath($xpath, 'return {{ELEMENT}}.childNodes.length');
Chris@14 278 *
Chris@14 279 * @param Element $element the webdriver element
Chris@14 280 * @param string $script the script to execute
Chris@14 281 * @param Boolean $sync whether to run the script synchronously (default is TRUE)
Chris@14 282 *
Chris@14 283 * @return mixed
Chris@14 284 */
Chris@14 285 private function executeJsOnElement(Element $element, $script, $sync = true)
Chris@14 286 {
Chris@14 287 $script = str_replace('{{ELEMENT}}', 'arguments[0]', $script);
Chris@14 288
Chris@14 289 $options = array(
Chris@14 290 'script' => $script,
Chris@14 291 'args' => array(array('ELEMENT' => $element->getID())),
Chris@14 292 );
Chris@14 293
Chris@14 294 if ($sync) {
Chris@14 295 return $this->wdSession->execute($options);
Chris@14 296 }
Chris@14 297
Chris@14 298 return $this->wdSession->execute_async($options);
Chris@14 299 }
Chris@14 300
Chris@14 301 /**
Chris@14 302 * {@inheritdoc}
Chris@14 303 */
Chris@14 304 public function start()
Chris@14 305 {
Chris@14 306 try {
Chris@14 307 $this->wdSession = $this->webDriver->session($this->browserName, $this->desiredCapabilities);
Chris@14 308 $this->applyTimeouts();
Chris@14 309 } catch (\Exception $e) {
Chris@14 310 throw new DriverException('Could not open connection: '.$e->getMessage(), 0, $e);
Chris@14 311 }
Chris@14 312
Chris@14 313 if (!$this->wdSession) {
Chris@14 314 throw new DriverException('Could not connect to a Selenium 2 / WebDriver server');
Chris@14 315 }
Chris@14 316 $this->started = true;
Chris@14 317 }
Chris@14 318
Chris@14 319 /**
Chris@14 320 * Sets the timeouts to apply to the webdriver session
Chris@14 321 *
Chris@14 322 * @param array $timeouts The session timeout settings: Array of {script, implicit, page} => time in milliseconds
Chris@14 323 *
Chris@14 324 * @throws DriverException
Chris@14 325 */
Chris@14 326 public function setTimeouts($timeouts)
Chris@14 327 {
Chris@14 328 $this->timeouts = $timeouts;
Chris@14 329
Chris@14 330 if ($this->isStarted()) {
Chris@14 331 $this->applyTimeouts();
Chris@14 332 }
Chris@14 333 }
Chris@14 334
Chris@14 335 /**
Chris@14 336 * Applies timeouts to the current session
Chris@14 337 */
Chris@14 338 private function applyTimeouts()
Chris@14 339 {
Chris@14 340 try {
Chris@14 341 foreach ($this->timeouts as $type => $param) {
Chris@14 342 $this->wdSession->timeouts($type, $param);
Chris@14 343 }
Chris@14 344 } catch (UnknownError $e) {
Chris@14 345 throw new DriverException('Error setting timeout: ' . $e->getMessage(), 0, $e);
Chris@14 346 }
Chris@14 347 }
Chris@14 348
Chris@14 349 /**
Chris@14 350 * {@inheritdoc}
Chris@14 351 */
Chris@14 352 public function isStarted()
Chris@14 353 {
Chris@14 354 return $this->started;
Chris@14 355 }
Chris@14 356
Chris@14 357 /**
Chris@14 358 * {@inheritdoc}
Chris@14 359 */
Chris@14 360 public function stop()
Chris@14 361 {
Chris@14 362 if (!$this->wdSession) {
Chris@14 363 throw new DriverException('Could not connect to a Selenium 2 / WebDriver server');
Chris@14 364 }
Chris@14 365
Chris@14 366 $this->started = false;
Chris@14 367 try {
Chris@14 368 $this->wdSession->close();
Chris@14 369 } catch (\Exception $e) {
Chris@14 370 throw new DriverException('Could not close connection', 0, $e);
Chris@14 371 }
Chris@14 372 }
Chris@14 373
Chris@14 374 /**
Chris@14 375 * {@inheritdoc}
Chris@14 376 */
Chris@14 377 public function reset()
Chris@14 378 {
Chris@14 379 $this->wdSession->deleteAllCookies();
Chris@14 380 }
Chris@14 381
Chris@14 382 /**
Chris@14 383 * {@inheritdoc}
Chris@14 384 */
Chris@14 385 public function visit($url)
Chris@14 386 {
Chris@14 387 $this->wdSession->open($url);
Chris@14 388 }
Chris@14 389
Chris@14 390 /**
Chris@14 391 * {@inheritdoc}
Chris@14 392 */
Chris@14 393 public function getCurrentUrl()
Chris@14 394 {
Chris@14 395 return $this->wdSession->url();
Chris@14 396 }
Chris@14 397
Chris@14 398 /**
Chris@14 399 * {@inheritdoc}
Chris@14 400 */
Chris@14 401 public function reload()
Chris@14 402 {
Chris@14 403 $this->wdSession->refresh();
Chris@14 404 }
Chris@14 405
Chris@14 406 /**
Chris@14 407 * {@inheritdoc}
Chris@14 408 */
Chris@14 409 public function forward()
Chris@14 410 {
Chris@14 411 $this->wdSession->forward();
Chris@14 412 }
Chris@14 413
Chris@14 414 /**
Chris@14 415 * {@inheritdoc}
Chris@14 416 */
Chris@14 417 public function back()
Chris@14 418 {
Chris@14 419 $this->wdSession->back();
Chris@14 420 }
Chris@14 421
Chris@14 422 /**
Chris@14 423 * {@inheritdoc}
Chris@14 424 */
Chris@14 425 public function switchToWindow($name = null)
Chris@14 426 {
Chris@14 427 $this->wdSession->focusWindow($name ? $name : '');
Chris@14 428 }
Chris@14 429
Chris@14 430 /**
Chris@14 431 * {@inheritdoc}
Chris@14 432 */
Chris@14 433 public function switchToIFrame($name = null)
Chris@14 434 {
Chris@14 435 $this->wdSession->frame(array('id' => $name));
Chris@14 436 }
Chris@14 437
Chris@14 438 /**
Chris@14 439 * {@inheritdoc}
Chris@14 440 */
Chris@14 441 public function setCookie($name, $value = null)
Chris@14 442 {
Chris@14 443 if (null === $value) {
Chris@14 444 $this->wdSession->deleteCookie($name);
Chris@14 445
Chris@14 446 return;
Chris@14 447 }
Chris@14 448
Chris@14 449 $cookieArray = array(
Chris@14 450 'name' => $name,
Chris@14 451 'value' => urlencode($value),
Chris@14 452 'secure' => false, // thanks, chibimagic!
Chris@14 453 );
Chris@14 454
Chris@14 455 $this->wdSession->setCookie($cookieArray);
Chris@14 456 }
Chris@14 457
Chris@14 458 /**
Chris@14 459 * {@inheritdoc}
Chris@14 460 */
Chris@14 461 public function getCookie($name)
Chris@14 462 {
Chris@14 463 $cookies = $this->wdSession->getAllCookies();
Chris@14 464 foreach ($cookies as $cookie) {
Chris@14 465 if ($cookie['name'] === $name) {
Chris@14 466 return urldecode($cookie['value']);
Chris@14 467 }
Chris@14 468 }
Chris@14 469 }
Chris@14 470
Chris@14 471 /**
Chris@14 472 * {@inheritdoc}
Chris@14 473 */
Chris@14 474 public function getContent()
Chris@14 475 {
Chris@14 476 return $this->wdSession->source();
Chris@14 477 }
Chris@14 478
Chris@14 479 /**
Chris@14 480 * {@inheritdoc}
Chris@14 481 */
Chris@14 482 public function getScreenshot()
Chris@14 483 {
Chris@14 484 return base64_decode($this->wdSession->screenshot());
Chris@14 485 }
Chris@14 486
Chris@14 487 /**
Chris@14 488 * {@inheritdoc}
Chris@14 489 */
Chris@14 490 public function getWindowNames()
Chris@14 491 {
Chris@14 492 return $this->wdSession->window_handles();
Chris@14 493 }
Chris@14 494
Chris@14 495 /**
Chris@14 496 * {@inheritdoc}
Chris@14 497 */
Chris@14 498 public function getWindowName()
Chris@14 499 {
Chris@14 500 return $this->wdSession->window_handle();
Chris@14 501 }
Chris@14 502
Chris@14 503 /**
Chris@14 504 * {@inheritdoc}
Chris@14 505 */
Chris@14 506 public function findElementXpaths($xpath)
Chris@14 507 {
Chris@14 508 $nodes = $this->wdSession->elements('xpath', $xpath);
Chris@14 509
Chris@14 510 $elements = array();
Chris@14 511 foreach ($nodes as $i => $node) {
Chris@14 512 $elements[] = sprintf('(%s)[%d]', $xpath, $i+1);
Chris@14 513 }
Chris@14 514
Chris@14 515 return $elements;
Chris@14 516 }
Chris@14 517
Chris@14 518 /**
Chris@14 519 * {@inheritdoc}
Chris@14 520 */
Chris@14 521 public function getTagName($xpath)
Chris@14 522 {
Chris@14 523 return $this->findElement($xpath)->name();
Chris@14 524 }
Chris@14 525
Chris@14 526 /**
Chris@14 527 * {@inheritdoc}
Chris@14 528 */
Chris@14 529 public function getText($xpath)
Chris@14 530 {
Chris@14 531 $node = $this->findElement($xpath);
Chris@14 532 $text = $node->text();
Chris@14 533 $text = (string) str_replace(array("\r", "\r\n", "\n"), ' ', $text);
Chris@14 534
Chris@14 535 return $text;
Chris@14 536 }
Chris@14 537
Chris@14 538 /**
Chris@14 539 * {@inheritdoc}
Chris@14 540 */
Chris@14 541 public function getHtml($xpath)
Chris@14 542 {
Chris@14 543 return $this->executeJsOnXpath($xpath, 'return {{ELEMENT}}.innerHTML;');
Chris@14 544 }
Chris@14 545
Chris@14 546 /**
Chris@14 547 * {@inheritdoc}
Chris@14 548 */
Chris@14 549 public function getOuterHtml($xpath)
Chris@14 550 {
Chris@14 551 return $this->executeJsOnXpath($xpath, 'return {{ELEMENT}}.outerHTML;');
Chris@14 552 }
Chris@14 553
Chris@14 554 /**
Chris@14 555 * {@inheritdoc}
Chris@14 556 */
Chris@14 557 public function getAttribute($xpath, $name)
Chris@14 558 {
Chris@14 559 $script = 'return {{ELEMENT}}.getAttribute(' . json_encode((string) $name) . ')';
Chris@14 560
Chris@14 561 return $this->executeJsOnXpath($xpath, $script);
Chris@14 562 }
Chris@14 563
Chris@14 564 /**
Chris@14 565 * {@inheritdoc}
Chris@14 566 */
Chris@14 567 public function getValue($xpath)
Chris@14 568 {
Chris@14 569 $element = $this->findElement($xpath);
Chris@14 570 $elementName = strtolower($element->name());
Chris@14 571 $elementType = strtolower($element->attribute('type'));
Chris@14 572
Chris@14 573 // Getting the value of a checkbox returns its value if selected.
Chris@14 574 if ('input' === $elementName && 'checkbox' === $elementType) {
Chris@14 575 return $element->selected() ? $element->attribute('value') : null;
Chris@14 576 }
Chris@14 577
Chris@14 578 if ('input' === $elementName && 'radio' === $elementType) {
Chris@14 579 $script = <<<JS
Chris@14 580 var node = {{ELEMENT}},
Chris@14 581 value = null;
Chris@14 582
Chris@14 583 var name = node.getAttribute('name');
Chris@14 584 if (name) {
Chris@14 585 var fields = window.document.getElementsByName(name),
Chris@14 586 i, l = fields.length;
Chris@14 587 for (i = 0; i < l; i++) {
Chris@14 588 var field = fields.item(i);
Chris@14 589 if (field.form === node.form && field.checked) {
Chris@14 590 value = field.value;
Chris@14 591 break;
Chris@14 592 }
Chris@14 593 }
Chris@14 594 }
Chris@14 595
Chris@14 596 return value;
Chris@14 597 JS;
Chris@14 598
Chris@14 599 return $this->executeJsOnElement($element, $script);
Chris@14 600 }
Chris@14 601
Chris@14 602 // Using $element->attribute('value') on a select only returns the first selected option
Chris@14 603 // even when it is a multiple select, so a custom retrieval is needed.
Chris@14 604 if ('select' === $elementName && $element->attribute('multiple')) {
Chris@14 605 $script = <<<JS
Chris@14 606 var node = {{ELEMENT}},
Chris@14 607 value = [];
Chris@14 608
Chris@14 609 for (var i = 0; i < node.options.length; i++) {
Chris@14 610 if (node.options[i].selected) {
Chris@14 611 value.push(node.options[i].value);
Chris@14 612 }
Chris@14 613 }
Chris@14 614
Chris@14 615 return value;
Chris@14 616 JS;
Chris@14 617
Chris@14 618 return $this->executeJsOnElement($element, $script);
Chris@14 619 }
Chris@14 620
Chris@14 621 return $element->attribute('value');
Chris@14 622 }
Chris@14 623
Chris@14 624 /**
Chris@14 625 * {@inheritdoc}
Chris@14 626 */
Chris@14 627 public function setValue($xpath, $value)
Chris@14 628 {
Chris@14 629 $element = $this->findElement($xpath);
Chris@14 630 $elementName = strtolower($element->name());
Chris@14 631
Chris@14 632 if ('select' === $elementName) {
Chris@14 633 if (is_array($value)) {
Chris@14 634 $this->deselectAllOptions($element);
Chris@14 635
Chris@14 636 foreach ($value as $option) {
Chris@14 637 $this->selectOptionOnElement($element, $option, true);
Chris@14 638 }
Chris@14 639
Chris@14 640 return;
Chris@14 641 }
Chris@14 642
Chris@14 643 $this->selectOptionOnElement($element, $value);
Chris@14 644
Chris@14 645 return;
Chris@14 646 }
Chris@14 647
Chris@14 648 if ('input' === $elementName) {
Chris@14 649 $elementType = strtolower($element->attribute('type'));
Chris@14 650
Chris@14 651 if (in_array($elementType, array('submit', 'image', 'button', 'reset'))) {
Chris@14 652 throw new DriverException(sprintf('Impossible to set value an element with XPath "%s" as it is not a select, textarea or textbox', $xpath));
Chris@14 653 }
Chris@14 654
Chris@14 655 if ('checkbox' === $elementType) {
Chris@14 656 if ($element->selected() xor (bool) $value) {
Chris@14 657 $this->clickOnElement($element);
Chris@14 658 }
Chris@14 659
Chris@14 660 return;
Chris@14 661 }
Chris@14 662
Chris@14 663 if ('radio' === $elementType) {
Chris@14 664 $this->selectRadioValue($element, $value);
Chris@14 665
Chris@14 666 return;
Chris@14 667 }
Chris@14 668
Chris@14 669 if ('file' === $elementType) {
Chris@14 670 $element->postValue(array('value' => array(strval($value))));
Chris@14 671
Chris@14 672 return;
Chris@14 673 }
Chris@14 674 }
Chris@14 675
Chris@14 676 $value = strval($value);
Chris@14 677
Chris@14 678 if (in_array($elementName, array('input', 'textarea'))) {
Chris@14 679 $existingValueLength = strlen($element->attribute('value'));
Chris@14 680 // Add the TAB key to ensure we unfocus the field as browsers are triggering the change event only
Chris@14 681 // after leaving the field.
Chris@14 682 $value = str_repeat(Key::BACKSPACE . Key::DELETE, $existingValueLength) . $value;
Chris@14 683 }
Chris@14 684
Chris@14 685 $element->postValue(array('value' => array($value)));
Chris@16 686 // Remove the focus from the element if the field still has focus in
Chris@16 687 // order to trigger the change event. By doing this instead of simply
Chris@16 688 // triggering the change event for the given xpath we ensure that the
Chris@16 689 // change event will not be triggered twice for the same element if it
Chris@16 690 // has lost focus in the meanwhile. If the element has lost focus
Chris@16 691 // already then there is nothing to do as this will already have caused
Chris@16 692 // the triggering of the change event for that element.
Chris@16 693 $script = <<<JS
Chris@16 694 var node = {{ELEMENT}};
Chris@16 695 if (document.activeElement === node) {
Chris@16 696 document.activeElement.blur();
Chris@16 697 }
Chris@16 698 JS;
Chris@16 699 $this->executeJsOnElement($element, $script);
Chris@14 700 }
Chris@14 701
Chris@14 702 /**
Chris@14 703 * {@inheritdoc}
Chris@14 704 */
Chris@14 705 public function check($xpath)
Chris@14 706 {
Chris@14 707 $element = $this->findElement($xpath);
Chris@14 708 $this->ensureInputType($element, $xpath, 'checkbox', 'check');
Chris@14 709
Chris@14 710 if ($element->selected()) {
Chris@14 711 return;
Chris@14 712 }
Chris@14 713
Chris@14 714 $this->clickOnElement($element);
Chris@14 715 }
Chris@14 716
Chris@14 717 /**
Chris@14 718 * {@inheritdoc}
Chris@14 719 */
Chris@14 720 public function uncheck($xpath)
Chris@14 721 {
Chris@14 722 $element = $this->findElement($xpath);
Chris@14 723 $this->ensureInputType($element, $xpath, 'checkbox', 'uncheck');
Chris@14 724
Chris@14 725 if (!$element->selected()) {
Chris@14 726 return;
Chris@14 727 }
Chris@14 728
Chris@14 729 $this->clickOnElement($element);
Chris@14 730 }
Chris@14 731
Chris@14 732 /**
Chris@14 733 * {@inheritdoc}
Chris@14 734 */
Chris@14 735 public function isChecked($xpath)
Chris@14 736 {
Chris@14 737 return $this->findElement($xpath)->selected();
Chris@14 738 }
Chris@14 739
Chris@14 740 /**
Chris@14 741 * {@inheritdoc}
Chris@14 742 */
Chris@14 743 public function selectOption($xpath, $value, $multiple = false)
Chris@14 744 {
Chris@14 745 $element = $this->findElement($xpath);
Chris@14 746 $tagName = strtolower($element->name());
Chris@14 747
Chris@14 748 if ('input' === $tagName && 'radio' === strtolower($element->attribute('type'))) {
Chris@14 749 $this->selectRadioValue($element, $value);
Chris@14 750
Chris@14 751 return;
Chris@14 752 }
Chris@14 753
Chris@14 754 if ('select' === $tagName) {
Chris@14 755 $this->selectOptionOnElement($element, $value, $multiple);
Chris@14 756
Chris@14 757 return;
Chris@14 758 }
Chris@14 759
Chris@14 760 throw new DriverException(sprintf('Impossible to select an option on the element with XPath "%s" as it is not a select or radio input', $xpath));
Chris@14 761 }
Chris@14 762
Chris@14 763 /**
Chris@14 764 * {@inheritdoc}
Chris@14 765 */
Chris@14 766 public function isSelected($xpath)
Chris@14 767 {
Chris@14 768 return $this->findElement($xpath)->selected();
Chris@14 769 }
Chris@14 770
Chris@14 771 /**
Chris@14 772 * {@inheritdoc}
Chris@14 773 */
Chris@14 774 public function click($xpath)
Chris@14 775 {
Chris@14 776 $this->clickOnElement($this->findElement($xpath));
Chris@14 777 }
Chris@14 778
Chris@14 779 private function clickOnElement(Element $element)
Chris@14 780 {
Chris@14 781 try {
Chris@14 782 // Move the mouse to the element as Selenium does not allow clicking on an element which is outside the viewport
Chris@14 783 $this->wdSession->moveto(array('element' => $element->getID()));
Chris@14 784 } catch (UnknownCommand $e) {
Chris@14 785 // If the Webdriver implementation does not support moveto (which is not part of the W3C WebDriver spec), proceed to the click
Chris@14 786 }
Chris@14 787
Chris@14 788 $element->click();
Chris@14 789 }
Chris@14 790
Chris@14 791 /**
Chris@14 792 * {@inheritdoc}
Chris@14 793 */
Chris@14 794 public function doubleClick($xpath)
Chris@14 795 {
Chris@14 796 $this->mouseOver($xpath);
Chris@14 797 $this->wdSession->doubleclick();
Chris@14 798 }
Chris@14 799
Chris@14 800 /**
Chris@14 801 * {@inheritdoc}
Chris@14 802 */
Chris@14 803 public function rightClick($xpath)
Chris@14 804 {
Chris@14 805 $this->mouseOver($xpath);
Chris@14 806 $this->wdSession->click(array('button' => 2));
Chris@14 807 }
Chris@14 808
Chris@14 809 /**
Chris@14 810 * {@inheritdoc}
Chris@14 811 */
Chris@14 812 public function attachFile($xpath, $path)
Chris@14 813 {
Chris@14 814 $element = $this->findElement($xpath);
Chris@14 815 $this->ensureInputType($element, $xpath, 'file', 'attach a file on');
Chris@14 816
Chris@14 817 // Upload the file to Selenium and use the remote path. This will
Chris@14 818 // ensure that Selenium always has access to the file, even if it runs
Chris@14 819 // as a remote instance.
Chris@14 820 try {
Chris@14 821 $remotePath = $this->uploadFile($path);
Chris@14 822 } catch (\Exception $e) {
Chris@14 823 // File could not be uploaded to remote instance. Use the local path.
Chris@14 824 $remotePath = $path;
Chris@14 825 }
Chris@14 826
Chris@14 827 $element->postValue(array('value' => array($remotePath)));
Chris@14 828 }
Chris@14 829
Chris@14 830 /**
Chris@14 831 * {@inheritdoc}
Chris@14 832 */
Chris@14 833 public function isVisible($xpath)
Chris@14 834 {
Chris@14 835 return $this->findElement($xpath)->displayed();
Chris@14 836 }
Chris@14 837
Chris@14 838 /**
Chris@14 839 * {@inheritdoc}
Chris@14 840 */
Chris@14 841 public function mouseOver($xpath)
Chris@14 842 {
Chris@14 843 $this->wdSession->moveto(array(
Chris@14 844 'element' => $this->findElement($xpath)->getID()
Chris@14 845 ));
Chris@14 846 }
Chris@14 847
Chris@14 848 /**
Chris@14 849 * {@inheritdoc}
Chris@14 850 */
Chris@14 851 public function focus($xpath)
Chris@14 852 {
Chris@14 853 $this->trigger($xpath, 'focus');
Chris@14 854 }
Chris@14 855
Chris@14 856 /**
Chris@14 857 * {@inheritdoc}
Chris@14 858 */
Chris@14 859 public function blur($xpath)
Chris@14 860 {
Chris@14 861 $this->trigger($xpath, 'blur');
Chris@14 862 }
Chris@14 863
Chris@14 864 /**
Chris@14 865 * {@inheritdoc}
Chris@14 866 */
Chris@14 867 public function keyPress($xpath, $char, $modifier = null)
Chris@14 868 {
Chris@14 869 $options = self::charToOptions($char, $modifier);
Chris@14 870 $this->trigger($xpath, 'keypress', $options);
Chris@14 871 }
Chris@14 872
Chris@14 873 /**
Chris@14 874 * {@inheritdoc}
Chris@14 875 */
Chris@14 876 public function keyDown($xpath, $char, $modifier = null)
Chris@14 877 {
Chris@14 878 $options = self::charToOptions($char, $modifier);
Chris@14 879 $this->trigger($xpath, 'keydown', $options);
Chris@14 880 }
Chris@14 881
Chris@14 882 /**
Chris@14 883 * {@inheritdoc}
Chris@14 884 */
Chris@14 885 public function keyUp($xpath, $char, $modifier = null)
Chris@14 886 {
Chris@14 887 $options = self::charToOptions($char, $modifier);
Chris@14 888 $this->trigger($xpath, 'keyup', $options);
Chris@14 889 }
Chris@14 890
Chris@14 891 /**
Chris@14 892 * {@inheritdoc}
Chris@14 893 */
Chris@14 894 public function dragTo($sourceXpath, $destinationXpath)
Chris@14 895 {
Chris@14 896 $source = $this->findElement($sourceXpath);
Chris@14 897 $destination = $this->findElement($destinationXpath);
Chris@14 898
Chris@14 899 $this->wdSession->moveto(array(
Chris@14 900 'element' => $source->getID()
Chris@14 901 ));
Chris@14 902
Chris@14 903 $script = <<<JS
Chris@14 904 (function (element) {
Chris@14 905 var event = document.createEvent("HTMLEvents");
Chris@14 906
Chris@14 907 event.initEvent("dragstart", true, true);
Chris@14 908 event.dataTransfer = {};
Chris@14 909
Chris@14 910 element.dispatchEvent(event);
Chris@14 911 }({{ELEMENT}}));
Chris@14 912 JS;
Chris@14 913 $this->withSyn()->executeJsOnElement($source, $script);
Chris@14 914
Chris@14 915 $this->wdSession->buttondown();
Chris@14 916 $this->wdSession->moveto(array(
Chris@14 917 'element' => $destination->getID()
Chris@14 918 ));
Chris@14 919 $this->wdSession->buttonup();
Chris@14 920
Chris@14 921 $script = <<<JS
Chris@14 922 (function (element) {
Chris@14 923 var event = document.createEvent("HTMLEvents");
Chris@14 924
Chris@14 925 event.initEvent("drop", true, true);
Chris@14 926 event.dataTransfer = {};
Chris@14 927
Chris@14 928 element.dispatchEvent(event);
Chris@14 929 }({{ELEMENT}}));
Chris@14 930 JS;
Chris@14 931 $this->withSyn()->executeJsOnElement($destination, $script);
Chris@14 932 }
Chris@14 933
Chris@14 934 /**
Chris@14 935 * {@inheritdoc}
Chris@14 936 */
Chris@14 937 public function executeScript($script)
Chris@14 938 {
Chris@14 939 if (preg_match('/^function[\s\(]/', $script)) {
Chris@14 940 $script = preg_replace('/;$/', '', $script);
Chris@14 941 $script = '(' . $script . ')';
Chris@14 942 }
Chris@14 943
Chris@14 944 $this->wdSession->execute(array('script' => $script, 'args' => array()));
Chris@14 945 }
Chris@14 946
Chris@14 947 /**
Chris@14 948 * {@inheritdoc}
Chris@14 949 */
Chris@14 950 public function evaluateScript($script)
Chris@14 951 {
Chris@14 952 if (0 !== strpos(trim($script), 'return ')) {
Chris@14 953 $script = 'return ' . $script;
Chris@14 954 }
Chris@14 955
Chris@14 956 return $this->wdSession->execute(array('script' => $script, 'args' => array()));
Chris@14 957 }
Chris@14 958
Chris@14 959 /**
Chris@14 960 * {@inheritdoc}
Chris@14 961 */
Chris@14 962 public function wait($timeout, $condition)
Chris@14 963 {
Chris@14 964 $script = "return $condition;";
Chris@14 965 $start = microtime(true);
Chris@14 966 $end = $start + $timeout / 1000.0;
Chris@14 967
Chris@14 968 do {
Chris@14 969 $result = $this->wdSession->execute(array('script' => $script, 'args' => array()));
Chris@14 970 usleep(100000);
Chris@14 971 } while (microtime(true) < $end && !$result);
Chris@14 972
Chris@14 973 return (bool) $result;
Chris@14 974 }
Chris@14 975
Chris@14 976 /**
Chris@14 977 * {@inheritdoc}
Chris@14 978 */
Chris@14 979 public function resizeWindow($width, $height, $name = null)
Chris@14 980 {
Chris@14 981 $this->wdSession->window($name ? $name : 'current')->postSize(
Chris@14 982 array('width' => $width, 'height' => $height)
Chris@14 983 );
Chris@14 984 }
Chris@14 985
Chris@14 986 /**
Chris@14 987 * {@inheritdoc}
Chris@14 988 */
Chris@14 989 public function submitForm($xpath)
Chris@14 990 {
Chris@14 991 $this->findElement($xpath)->submit();
Chris@14 992 }
Chris@14 993
Chris@14 994 /**
Chris@14 995 * {@inheritdoc}
Chris@14 996 */
Chris@14 997 public function maximizeWindow($name = null)
Chris@14 998 {
Chris@14 999 $this->wdSession->window($name ? $name : 'current')->maximize();
Chris@14 1000 }
Chris@14 1001
Chris@14 1002 /**
Chris@14 1003 * Returns Session ID of WebDriver or `null`, when session not started yet.
Chris@14 1004 *
Chris@14 1005 * @return string|null
Chris@14 1006 */
Chris@14 1007 public function getWebDriverSessionId()
Chris@14 1008 {
Chris@14 1009 return $this->isStarted() ? basename($this->wdSession->getUrl()) : null;
Chris@14 1010 }
Chris@14 1011
Chris@14 1012 /**
Chris@14 1013 * @param string $xpath
Chris@14 1014 *
Chris@14 1015 * @return Element
Chris@14 1016 */
Chris@14 1017 private function findElement($xpath)
Chris@14 1018 {
Chris@14 1019 return $this->wdSession->element('xpath', $xpath);
Chris@14 1020 }
Chris@14 1021
Chris@14 1022 /**
Chris@14 1023 * Selects a value in a radio button group
Chris@14 1024 *
Chris@14 1025 * @param Element $element An element referencing one of the radio buttons of the group
Chris@14 1026 * @param string $value The value to select
Chris@14 1027 *
Chris@14 1028 * @throws DriverException when the value cannot be found
Chris@14 1029 */
Chris@14 1030 private function selectRadioValue(Element $element, $value)
Chris@14 1031 {
Chris@14 1032 // short-circuit when we already have the right button of the group to avoid XPath queries
Chris@14 1033 if ($element->attribute('value') === $value) {
Chris@14 1034 $element->click();
Chris@14 1035
Chris@14 1036 return;
Chris@14 1037 }
Chris@14 1038
Chris@14 1039 $name = $element->attribute('name');
Chris@14 1040
Chris@14 1041 if (!$name) {
Chris@14 1042 throw new DriverException(sprintf('The radio button does not have the value "%s"', $value));
Chris@14 1043 }
Chris@14 1044
Chris@14 1045 $formId = $element->attribute('form');
Chris@14 1046
Chris@14 1047 try {
Chris@14 1048 if (null !== $formId) {
Chris@14 1049 $xpath = <<<'XPATH'
Chris@14 1050 //form[@id=%1$s]//input[@type="radio" and not(@form) and @name=%2$s and @value = %3$s]
Chris@14 1051 |
Chris@14 1052 //input[@type="radio" and @form=%1$s and @name=%2$s and @value = %3$s]
Chris@14 1053 XPATH;
Chris@14 1054
Chris@14 1055 $xpath = sprintf(
Chris@14 1056 $xpath,
Chris@14 1057 $this->xpathEscaper->escapeLiteral($formId),
Chris@14 1058 $this->xpathEscaper->escapeLiteral($name),
Chris@14 1059 $this->xpathEscaper->escapeLiteral($value)
Chris@14 1060 );
Chris@14 1061 $input = $this->wdSession->element('xpath', $xpath);
Chris@14 1062 } else {
Chris@14 1063 $xpath = sprintf(
Chris@14 1064 './ancestor::form//input[@type="radio" and not(@form) and @name=%s and @value = %s]',
Chris@14 1065 $this->xpathEscaper->escapeLiteral($name),
Chris@14 1066 $this->xpathEscaper->escapeLiteral($value)
Chris@14 1067 );
Chris@14 1068 $input = $element->element('xpath', $xpath);
Chris@14 1069 }
Chris@14 1070 } catch (NoSuchElement $e) {
Chris@14 1071 $message = sprintf('The radio group "%s" does not have an option "%s"', $name, $value);
Chris@14 1072
Chris@14 1073 throw new DriverException($message, 0, $e);
Chris@14 1074 }
Chris@14 1075
Chris@14 1076 $input->click();
Chris@14 1077 }
Chris@14 1078
Chris@14 1079 /**
Chris@14 1080 * @param Element $element
Chris@14 1081 * @param string $value
Chris@14 1082 * @param bool $multiple
Chris@14 1083 */
Chris@14 1084 private function selectOptionOnElement(Element $element, $value, $multiple = false)
Chris@14 1085 {
Chris@14 1086 $escapedValue = $this->xpathEscaper->escapeLiteral($value);
Chris@14 1087 // The value of an option is the normalized version of its text when it has no value attribute
Chris@14 1088 $optionQuery = sprintf('.//option[@value = %s or (not(@value) and normalize-space(.) = %s)]', $escapedValue, $escapedValue);
Chris@14 1089 $option = $element->element('xpath', $optionQuery);
Chris@14 1090
Chris@14 1091 if ($multiple || !$element->attribute('multiple')) {
Chris@14 1092 if (!$option->selected()) {
Chris@14 1093 $option->click();
Chris@14 1094 }
Chris@14 1095
Chris@14 1096 return;
Chris@14 1097 }
Chris@14 1098
Chris@14 1099 // Deselect all options before selecting the new one
Chris@14 1100 $this->deselectAllOptions($element);
Chris@14 1101 $option->click();
Chris@14 1102 }
Chris@14 1103
Chris@14 1104 /**
Chris@14 1105 * Deselects all options of a multiple select
Chris@14 1106 *
Chris@14 1107 * Note: this implementation does not trigger a change event after deselecting the elements.
Chris@14 1108 *
Chris@14 1109 * @param Element $element
Chris@14 1110 */
Chris@14 1111 private function deselectAllOptions(Element $element)
Chris@14 1112 {
Chris@14 1113 $script = <<<JS
Chris@14 1114 var node = {{ELEMENT}};
Chris@14 1115 var i, l = node.options.length;
Chris@14 1116 for (i = 0; i < l; i++) {
Chris@14 1117 node.options[i].selected = false;
Chris@14 1118 }
Chris@14 1119 JS;
Chris@14 1120
Chris@14 1121 $this->executeJsOnElement($element, $script);
Chris@14 1122 }
Chris@14 1123
Chris@14 1124 /**
Chris@14 1125 * Ensures the element is a checkbox
Chris@14 1126 *
Chris@14 1127 * @param Element $element
Chris@14 1128 * @param string $xpath
Chris@14 1129 * @param string $type
Chris@14 1130 * @param string $action
Chris@14 1131 *
Chris@14 1132 * @throws DriverException
Chris@14 1133 */
Chris@14 1134 private function ensureInputType(Element $element, $xpath, $type, $action)
Chris@14 1135 {
Chris@14 1136 if ('input' !== strtolower($element->name()) || $type !== strtolower($element->attribute('type'))) {
Chris@14 1137 $message = 'Impossible to %s the element with XPath "%s" as it is not a %s input';
Chris@14 1138
Chris@14 1139 throw new DriverException(sprintf($message, $action, $xpath, $type));
Chris@14 1140 }
Chris@14 1141 }
Chris@14 1142
Chris@14 1143 /**
Chris@14 1144 * @param $xpath
Chris@14 1145 * @param $event
Chris@14 1146 * @param string $options
Chris@14 1147 */
Chris@14 1148 private function trigger($xpath, $event, $options = '{}')
Chris@14 1149 {
Chris@14 1150 $script = 'Syn.trigger("' . $event . '", ' . $options . ', {{ELEMENT}})';
Chris@14 1151 $this->withSyn()->executeJsOnXpath($xpath, $script);
Chris@14 1152 }
Chris@14 1153
Chris@14 1154 /**
Chris@14 1155 * Uploads a file to the Selenium instance.
Chris@14 1156 *
Chris@14 1157 * Note that uploading files is not part of the official WebDriver
Chris@14 1158 * specification, but it is supported by Selenium.
Chris@14 1159 *
Chris@14 1160 * @param string $path The path to the file to upload.
Chris@14 1161 *
Chris@14 1162 * @return string The remote path.
Chris@14 1163 *
Chris@14 1164 * @throws DriverException When PHP is compiled without zip support, or the file doesn't exist.
Chris@14 1165 * @throws UnknownError When an unknown error occurred during file upload.
Chris@14 1166 * @throws \Exception When a known error occurred during file upload.
Chris@14 1167 *
Chris@14 1168 * @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533
Chris@14 1169 */
Chris@14 1170 private function uploadFile($path)
Chris@14 1171 {
Chris@14 1172 if (!is_file($path)) {
Chris@14 1173 throw new DriverException('File does not exist locally and cannot be uploaded to the remote instance.');
Chris@14 1174 }
Chris@14 1175
Chris@14 1176 if (!class_exists('ZipArchive')) {
Chris@14 1177 throw new DriverException('Could not compress file, PHP is compiled without zip support.');
Chris@14 1178 }
Chris@14 1179
Chris@14 1180 // Selenium only accepts uploads that are compressed as a Zip archive.
Chris@14 1181 $tempFilename = tempnam('', 'WebDriverZip');
Chris@14 1182
Chris@14 1183 $archive = new \ZipArchive();
Chris@14 1184 $result = $archive->open($tempFilename, \ZipArchive::CREATE);
Chris@14 1185 if (!$result) {
Chris@14 1186 throw new DriverException('Zip archive could not be created. Error ' . $result);
Chris@14 1187 }
Chris@14 1188 $result = $archive->addFile($path, basename($path));
Chris@14 1189 if (!$result) {
Chris@14 1190 throw new DriverException('File could not be added to zip archive.');
Chris@14 1191 }
Chris@14 1192 $result = $archive->close();
Chris@14 1193 if (!$result) {
Chris@14 1194 throw new DriverException('Zip archive could not be closed.');
Chris@14 1195 }
Chris@14 1196
Chris@14 1197 try {
Chris@14 1198 $remotePath = $this->wdSession->file(array('file' => base64_encode(file_get_contents($tempFilename))));
Chris@14 1199
Chris@14 1200 // If no path is returned the file upload failed silently. In this
Chris@14 1201 // case it is possible Selenium was not used but another web driver
Chris@14 1202 // such as PhantomJS.
Chris@14 1203 // @todo Support other drivers when (if) they get remote file transfer
Chris@14 1204 // capability.
Chris@14 1205 if (empty($remotePath)) {
Chris@14 1206 throw new UnknownError();
Chris@14 1207 }
Chris@14 1208 } catch (\Exception $e) {
Chris@14 1209 // Catch any error so we can still clean up the temporary archive.
Chris@14 1210 }
Chris@14 1211
Chris@14 1212 unlink($tempFilename);
Chris@14 1213
Chris@14 1214 if (isset($e)) {
Chris@14 1215 throw $e;
Chris@14 1216 }
Chris@14 1217
Chris@14 1218 return $remotePath;
Chris@14 1219 }
Chris@14 1220
Chris@14 1221 }