From 8ff8c99cd283c94141343fa621a6dd8243458746 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Thu, 27 Oct 2016 16:35:47 +0300 Subject: [PATCH 1/8] Add support for uploading files to remote Selenium instances. Fixes #187. --- src/Selenium2Driver.php | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index a1a3e2d9..e345bad9 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -794,6 +794,11 @@ public function attachFile($xpath, $path) $element = $this->findElement($xpath); $this->ensureInputType($element, $xpath, 'file', 'attach a file on'); + // Upload the file to Selenium and use the remote path. This will + // ensure that Selenium always has access to the file, even if it runs + // as a remote instance. + $path = $this->uploadFile($path); + $element->postValue(array('value' => array($path))); } @@ -1120,4 +1125,39 @@ private function trigger($xpath, $event, $options = '{}') $script = 'Syn.trigger("' . $event . '", ' . $options . ', {{ELEMENT}})'; $this->withSyn()->executeJsOnXpath($xpath, $script); } + + /** + * Uploads a file to the Selenium instance. + * + * @param string $path The path to the file to upload. + * + * @return string The remote path. + * + * @throws DriverException When the file is not found. + */ + public function uploadFile($path) { + if (!is_file($path)) { + throw new DriverException('Could not upload file, the file: ' . $path . '. was not found.'); + } + + // Selenium only accepts uploads that are compressed as a Zip archive. + $temp_filename = tempnam('', 'WebDriverZip'); + + $archive = new \ZipArchive(); + $archive->open($temp_filename, \ZipArchive::CREATE); + $archive->addFile($path, basename($path)); + $archive->close(); + + // Note that uploading files is not part of the official WebDriver + // specification, but it is supported by Selenium. + // @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 + $remote_path = $this->getWebDriverSession()->file([ + 'file' => base64_encode(file_get_contents($temp_filename)), + ]); + + unlink($temp_filename); + + return $remote_path; + } + } From de3acde315e2663c2ee717694b628e73cbf0c51c Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Fri, 28 Oct 2016 16:38:00 +0300 Subject: [PATCH 2/8] Add support for local PhantomJS instances. --- src/Selenium2Driver.php | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index e345bad9..09c33efc 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -13,6 +13,7 @@ use Behat\Mink\Exception\DriverException; use Behat\Mink\Selector\Xpath\Escaper; use WebDriver\Element; +use WebDriver\Exception\InvalidRequest; use WebDriver\Exception\NoSuchElement; use WebDriver\Exception\UnknownError; use WebDriver\Exception; @@ -797,9 +798,15 @@ public function attachFile($xpath, $path) // Upload the file to Selenium and use the remote path. This will // ensure that Selenium always has access to the file, even if it runs // as a remote instance. - $path = $this->uploadFile($path); + try { + $remote_path = $this->uploadFile($path); + } + catch (InvalidRequest $e) { + // File could not be uploaded to remote instance. Use the local path. + $remote_path = $path; + } - $element->postValue(array('value' => array($path))); + $element->postValue(array('value' => array($remote_path))); } /** @@ -1129,11 +1136,16 @@ private function trigger($xpath, $event, $options = '{}') /** * Uploads a file to the Selenium instance. * + * Note that uploading files is not part of the official WebDriver + * specification, but it is supported by Selenium. + * @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 + * * @param string $path The path to the file to upload. * * @return string The remote path. * * @throws DriverException When the file is not found. + * @throws InvalidRequest When the driver does not support file uploads. */ public function uploadFile($path) { if (!is_file($path)) { @@ -1148,15 +1160,23 @@ public function uploadFile($path) { $archive->addFile($path, basename($path)); $archive->close(); - // Note that uploading files is not part of the official WebDriver - // specification, but it is supported by Selenium. - // @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 - $remote_path = $this->getWebDriverSession()->file([ - 'file' => base64_encode(file_get_contents($temp_filename)), - ]); + try { + $remote_path = $this->getWebDriverSession()->file(array('file' => base64_encode(file_get_contents($temp_filename)))); + } + catch (InvalidRequest $e) { + // Catch the error so we can still clean up the temporary archive. + } unlink($temp_filename); + // If the file upload failed, then probably Selenium was not used but + // another web driver such as PhantomJS. + // @todo Support other drivers when (if) they get remote file transfer + // capability. + if (empty($remote_path)) { + throw new InvalidRequest(); + } + return $remote_path; } From c6fded0407af728e04b0c08017913e53bf916325 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Fri, 28 Oct 2016 16:58:06 +0300 Subject: [PATCH 3/8] Test a remote Selenium instance. --- .travis.yml | 5 +++++ bin/run-selenium-remote.sh | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 bin/run-selenium-remote.sh diff --git a/.travis.yml b/.travis.yml index a94e7a94..055a9c19 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,11 @@ matrix: env: WEBDRIVER=phantomjs - php: 5.5 env: WEBDRIVER=phantomjs PHANTOM_VERSION=2 + - php: 7.0 + env: WEBDRIVER=selenium-remote + sudo: required + services: + - docker before_script: - sh bin/run-"$WEBDRIVER".sh diff --git a/bin/run-selenium-remote.sh b/bin/run-selenium-remote.sh new file mode 100644 index 00000000..eabc8134 --- /dev/null +++ b/bin/run-selenium-remote.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env sh +set -e + +echo ' Downloading selenium' +docker pull selenium/standalone-chrome +echo ' Running selenium' +docker run -d -p 4444:4444 --network=host selenium/standalone-chrome From f519ea6af6ced849156ccd8eb2da1024ac4a72b9 Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Fri, 28 Oct 2016 17:55:33 +0300 Subject: [PATCH 4/8] Use Firefox instead of Chrome for the remote Selenium instance. --- bin/run-selenium-remote.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/run-selenium-remote.sh b/bin/run-selenium-remote.sh index eabc8134..9d2205d1 100644 --- a/bin/run-selenium-remote.sh +++ b/bin/run-selenium-remote.sh @@ -2,6 +2,6 @@ set -e echo ' Downloading selenium' -docker pull selenium/standalone-chrome +docker pull selenium/standalone-firefox:2.53.1 echo ' Running selenium' -docker run -d -p 4444:4444 --network=host selenium/standalone-chrome +docker run -d -p 4444:4444 --network=host selenium/standalone-firefox:2.53.1 From 3bff87e04c3bea237fb90ed720108090fdc5ebaf Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Sat, 29 Oct 2016 00:53:58 +0300 Subject: [PATCH 5/8] Fix review remarks. --- src/Selenium2Driver.php | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index 09c33efc..6efff60d 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -801,7 +801,7 @@ public function attachFile($xpath, $path) try { $remote_path = $this->uploadFile($path); } - catch (InvalidRequest $e) { + catch (\Exception $e) { // File could not be uploaded to remote instance. Use the local path. $remote_path = $path; } @@ -1144,12 +1144,13 @@ private function trigger($xpath, $event, $options = '{}') * * @return string The remote path. * - * @throws DriverException When the file is not found. - * @throws InvalidRequest When the driver does not support file uploads. + * @throws DriverException When PHP is compiled without zip support. + * @throws UnknownError When an unknown error occurred during file upload. */ - public function uploadFile($path) { - if (!is_file($path)) { - throw new DriverException('Could not upload file, the file: ' . $path . '. was not found.'); + public function uploadFile($path) + { + if (!class_exists('ZipArchive')) { + throw new DriverException('Could not upload compressed file, PHP is compiled without zip support.'); } // Selenium only accepts uploads that are compressed as a Zip archive. @@ -1161,20 +1162,25 @@ public function uploadFile($path) { $archive->close(); try { - $remote_path = $this->getWebDriverSession()->file(array('file' => base64_encode(file_get_contents($temp_filename)))); + $remote_path = $this->wdSession->file(array('file' => base64_encode(file_get_contents($temp_filename)))); + + // If no path is returned the file upload failed silently. In this + // case it is possible Selenium was not used but another web driver + // such as PhantomJS. + // @todo Support other drivers when (if) they get remote file transfer + // capability. + if (empty($remote_path)) { + throw new UnknownError(); + } } - catch (InvalidRequest $e) { - // Catch the error so we can still clean up the temporary archive. + catch (\Exception $e) { + // Catch any error so we can still clean up the temporary archive. } unlink($temp_filename); - // If the file upload failed, then probably Selenium was not used but - // another web driver such as PhantomJS. - // @todo Support other drivers when (if) they get remote file transfer - // capability. - if (empty($remote_path)) { - throw new InvalidRequest(); + if (isset($e)) { + throw $e; } return $remote_path; From 0a1833159b14dccd9b7ad5f02f9a048f7a9519ac Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Sat, 29 Oct 2016 01:23:06 +0300 Subject: [PATCH 6/8] Add documentation. --- src/Selenium2Driver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index 6efff60d..5e54b48f 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -1146,6 +1146,7 @@ private function trigger($xpath, $event, $options = '{}') * * @throws DriverException When PHP is compiled without zip support. * @throws UnknownError When an unknown error occurred during file upload. + * @throws \Exception When a known error occurred during file upload. */ public function uploadFile($path) { From 2064e99b793b6a6258d04c7adcf7576616385dbc Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Sat, 29 Oct 2016 01:28:28 +0300 Subject: [PATCH 7/8] Throw a helpful exception when the file to upload doesn't exist locally. --- src/Selenium2Driver.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index 5e54b48f..0aa5daec 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -1144,14 +1144,18 @@ private function trigger($xpath, $event, $options = '{}') * * @return string The remote path. * - * @throws DriverException When PHP is compiled without zip support. + * @throws DriverException When PHP is compiled without zip support, or the file doesn't exist. * @throws UnknownError When an unknown error occurred during file upload. * @throws \Exception When a known error occurred during file upload. */ public function uploadFile($path) { + if (!is_file($path)) { + throw new DriverException('File does not exist locally and cannot be uploaded to the remote instance.'); + } + if (!class_exists('ZipArchive')) { - throw new DriverException('Could not upload compressed file, PHP is compiled without zip support.'); + throw new DriverException('Could not compress file, PHP is compiled without zip support.'); } // Selenium only accepts uploads that are compressed as a Zip archive. From 506a2a0fb3d148a45bfb17ba3463e89c104ddafe Mon Sep 17 00:00:00 2001 From: Pieter Frenssen Date: Sat, 29 Oct 2016 13:19:03 +0300 Subject: [PATCH 8/8] Fix review remarks. --- src/Selenium2Driver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Selenium2Driver.php b/src/Selenium2Driver.php index 0aa5daec..a83c9073 100755 --- a/src/Selenium2Driver.php +++ b/src/Selenium2Driver.php @@ -13,7 +13,6 @@ use Behat\Mink\Exception\DriverException; use Behat\Mink\Selector\Xpath\Escaper; use WebDriver\Element; -use WebDriver\Exception\InvalidRequest; use WebDriver\Exception\NoSuchElement; use WebDriver\Exception\UnknownError; use WebDriver\Exception; @@ -1138,7 +1137,6 @@ private function trigger($xpath, $event, $options = '{}') * * Note that uploading files is not part of the official WebDriver * specification, but it is supported by Selenium. - * @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 * * @param string $path The path to the file to upload. * @@ -1147,6 +1145,8 @@ private function trigger($xpath, $event, $options = '{}') * @throws DriverException When PHP is compiled without zip support, or the file doesn't exist. * @throws UnknownError When an unknown error occurred during file upload. * @throws \Exception When a known error occurred during file upload. + * + * @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 */ public function uploadFile($path) {