Skip to content
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

Merged
merged 8 commits into from
Oct 29, 2016

Conversation

pfrenssen
Copy link
Contributor

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.

@pfrenssen
Copy link
Contributor Author

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
Copy link
Member

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.

Copy link
Contributor Author

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.

@pfrenssen
Copy link
Contributor Author

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)) {
Copy link
Member

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?

Copy link
Contributor Author

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) {
Copy link
Member

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();
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point!

Copy link
Member

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

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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))));
Copy link
Member

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)) {
Copy link
Member

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) {
Copy link
Member

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?

@pfrenssen
Copy link
Contributor Author

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
Copy link
Member

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.

@aik099
Copy link
Member

aik099 commented Oct 29, 2016

Except moving of @see tag the code looks good to me.

@@ -13,6 +13,7 @@
use Behat\Mink\Exception\DriverException;
use Behat\Mink\Selector\Xpath\Escaper;
use WebDriver\Element;
use WebDriver\Exception\InvalidRequest;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pfrenssen
Copy link
Contributor Author

Thanks for the quick reviews! I have addressed the remarks.

@aik099
Copy link
Member

aik099 commented Oct 29, 2016

  1. have you actually checked, that new tests will fail without new library code changes?
  2. when test suite is running locally on developer machine all will work properly as before, but of course no remote upload tests?

@pfrenssen
Copy link
Contributor Author

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.

@pfrenssen
Copy link
Contributor Author

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 1 uploaded file is missing after some_file.txt. This proves that without the fix the file is not uploaded to the remote instance.

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 1 uploaded file is present in the response, so the file is correctly uploaded to the remote instance.

@aik099
Copy link
Member

aik099 commented Oct 29, 2016

That's exactly what I've meant. Good.

@aik099 aik099 merged commit 721cbba into minkphp:master Oct 29, 2016
@aik099
Copy link
Member

aik099 commented Oct 29, 2016

Merging, thanks @pfrenssen .

@frankcarey
Copy link

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);
Copy link
Member

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) {
Copy link
Member

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)
Copy link
Member

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);
Copy link
Member

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

@aik099
Copy link
Member

aik099 commented Dec 14, 2016

@stof , thanks for reviewing, but PR is already merged. All the fixes should go as separate PR I guess.

@stof
Copy link
Member

stof commented Dec 14, 2016

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.
Doing a new PR for the fixes is indeed the way to go (if it is not done at the time I work on releasing, I will do it myself, as I don't want to release with the method exposed publicly and so covered by BC)

@pfrenssen
Copy link
Contributor Author

Thanks for the review. I will open a followup to address the remarks. Will link it here when I make the PR.

pfrenssen added a commit to pfrenssen/MinkSelenium2Driver that referenced this pull request Dec 15, 2016
@volkyeth
Copy link

Is there any roadmap for the next release yet? This feature by itself would deserve a release.

@asprega
Copy link

asprega commented Jan 1, 2017

+1. Is there a planned release for this feature?

stof added a commit that referenced this pull request Jan 3, 2017
Fix review remarks. Followup for #252.
@kvdnberg
Copy link

kvdnberg commented May 4, 2017

+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 ;)

@pfrenssen
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants