-
Notifications
You must be signed in to change notification settings - Fork 163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue #187: Add support for uploading files to remote Selenium instance #252
Conversation
This still needs some work. There's a PHP error, but I also see that it now fails on a local instance of PhantomJS when running in webdriver mode. |
set -e | ||
|
||
echo ' Downloading selenium' | ||
docker pull selenium/standalone-firefox:2.53.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This uses Docker on Travis CI? I wonder how slow that might me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's quite comparable it seems, the build takes about 30 seconds longer. It looks like this time is needed to download the docker image.
I think this is the most cost effective way of testing a remote instance of Selenium. If we want to try a "real" remote then we probably have to go with a hosted service like Browserstack.
This is ready for a review. The tests are failing but this is due to the known problem #245 |
* @throws InvalidRequest When the driver does not support file uploads. | ||
*/ | ||
public function uploadFile($path) { | ||
if (!is_file($path)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There wasn't such check before. Is it even needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly needed :)
* @throws DriverException When the file is not found. | ||
* @throws InvalidRequest When the driver does not support file uploads. | ||
*/ | ||
public function uploadFile($path) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The opening {
of function declaration should be placed on next line.
P.S.
I wonder why Scrutinizer CI haven't picked that up.
// Selenium only accepts uploads that are compressed as a Zip archive. | ||
$temp_filename = tempnam('', 'WebDriverZip'); | ||
|
||
$archive = new \ZipArchive(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ZipArchive
class present in any PHP installation. If it's possible for some OS to have limited PHP installation, then we'd better check that such class exists and throw useful exception instead of getting Fatal Error on missing ZipArchive
class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also it seems, that you always are using ZipArchive
class now.
To preserve BC I think, that:
- in case if class is absent and we know it's remote file (not exists locally) then throw
DriverException
- in case case if class is absent and it's local file just upload file the old way without even creating
ZipArchive
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is not replacing the uploading that was done before. That part is still handled by Selenium2Driver::attachFile()
which ultimately does the sendKeys()
webdriver command to attach the file to the file field. What this does is attempt to transfer the file from the local environment to the remote instance.
Do you think it would be maybe a good idea to rename this method to transferFile()
to make this clearer?
When doing this transfer the file always needs to be zipped, it never works otherwise. If you try to transfer an unzipped file to Selenium you get this error:
Expected there to be only 1 file. There were: 0
Build info: version: '3.0.0', revision: '350cf60', time: '2016-10-13 10:48:16 -0700'
System info: host: 'N/A', ip: 'N/A', os.name: 'Linux', os.arch: 'amd64', os.version: '4.8.4-1-ARCH', java.version: '1.8.0_91'
Driver info: driver.version: unknown
Note that it is not possible to figure out if Selenium runs locally or remotely. A remote instance might be forwarded to a local port, appearing exactly the same to us as a local instance. So the only thing we can do is to attempt a transfer. When this succeeds we get a remote path back which we can use. When anything goes wrong we catch this and continue as normal with the original file path.
I've restructured it a bit now so that it works as you suggest, any error throws an exception which will be caught and the file will be attached in the old way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then when ZipArchive
class isn't available we should execute old code immediately without throwing an exception and let driver fail as it fails currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that is what is going to happen now. If ZipArchive
is not there a DriverException
is thrown, and this is caught by attachFile()
which will then continue with the original (local) path. It will then fail exactly as it does now later on when the sendKeys
command is executed.
$archive->close(); | ||
|
||
try { | ||
$remote_path = $this->getWebDriverSession()->file(array('file' => base64_encode(file_get_contents($temp_filename)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use $this->wdSession
directly (as done in other methods of the driver) instead of calling $this->getWebDriverSession()
method.
// another web driver such as PhantomJS. | ||
// @todo Support other drivers when (if) they get remote file transfer | ||
// capability. | ||
if (empty($remote_path)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to replace with this:
if (isset($e)) {
throw $e;
}
The above version:
- doesn't recreate exception based
- just reuses exception caught before
try { | ||
$remote_path = $this->getWebDriverSession()->file(array('file' => base64_encode(file_get_contents($temp_filename)))); | ||
} | ||
catch (InvalidRequest $e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is InvalidRequest
the only possible error from file upload operation?
Maybe we should catch and rethrow all exceptions?
Addressed the remarks. This is ready for another look :) |
* | ||
* 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move @see
tag after @throws
. Or in any place, where other methods in this project has @see
tag placed.
Except moving of |
@@ -13,6 +13,7 @@ | |||
use Behat\Mink\Exception\DriverException; | |||
use Behat\Mink\Selector\Xpath\Escaper; | |||
use WebDriver\Element; | |||
use WebDriver\Exception\InvalidRequest; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Scrutinizer CI says that this class import isn't used anymore: https://scrutinizer-ci.com/g/minkphp/MinkSelenium2Driver/inspections/89c102e8-c656-4124-b660-6129c7ec2f16/patches?status=new
Thanks for the quick reviews! I have addressed the remarks. |
|
I don't fully understand the question. I need this fix because we are running our tests on continuousphp which recently switched to remote Selenium instances and our uploads were failing. I have tested on my dev machine with local and remote instances of Selenium. It's now also working on continuousphp which it wasn't before. |
Oh you probably mean to run the tests on Travis using a remote Selenium instance, but without the fix. I made a branch containing only the test, but without the fix. Here is the relevant test result: https://travis-ci.org/pfrenssen/MinkSelenium2Driver/jobs/171671291 You can see that in the response the text Here is the same test result with the fix: https://travis-ci.org/pfrenssen/MinkSelenium2Driver/jobs/171596712 In this case you can see that the text |
That's exactly what I've meant. Good. |
Merging, thanks @pfrenssen . |
This was such a pain for so long, THANK YOU! |
// ensure that Selenium always has access to the file, even if it runs | ||
// as a remote instance. | ||
try { | ||
$remote_path = $this->uploadFile($path); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please use camelCase variables names
try { | ||
$remote_path = $this->uploadFile($path); | ||
} | ||
catch (\Exception $e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be on the previous line
* | ||
* @see https://github.com/SeleniumHQ/selenium/blob/master/py/selenium/webdriver/remote/webelement.py#L533 | ||
*/ | ||
public function uploadFile($path) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be private
$temp_filename = tempnam('', 'WebDriverZip'); | ||
|
||
$archive = new \ZipArchive(); | ||
$archive->open($temp_filename, \ZipArchive::CREATE); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you need to check the return value here, as ZipArchive uses the return value to handle errors instead of throwing exceptions
@stof , thanks for reviewing, but PR is already merged. All the fixes should go as separate PR I guess. |
yeah, I received a notification about this PR because of the comment, and I reviewed it while noticing it was merged only at the end. |
Thanks for the review. I will open a followup to address the remarks. Will link it here when I make the PR. |
Is there any roadmap for the next release yet? This feature by itself would deserve a release. |
+1. Is there a planned release for this feature? |
Fix review remarks. Followup for #252.
+1 for release! We spend days in agony trying to fix this problem while testing CircleCI 2.0 with docker images (which can't share volumes). When I finally found this yesterday I was thrilled. One note: I had to debug it to get it to work because my docker php didn't have zip support and because the exception is caught and the 'local' file is then used, it seemed like this didn't fix the problem. So if anyone else is checking this fix and it doesn't seem to work, check your zip extension ;) |
I made a separate issue that suggests to make a new release, the comments here are probably not going to be seen since this is already closed: #269 |
Here's a proposed fix for #187. This works both on local and remote instances. I tested using a local installation of Selenium and a Docker instance.